]>
Commit | Line | Data |
---|---|---|
7460295f | 1 | // ShellInABox.js -- Use XMLHttpRequest to provide an AJAX terminal emulator. |
e0bb8a33 | 2 | // Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com> |
7460295f MG |
3 | // |
4 | // This program is free software; you can redistribute it and/or modify | |
5 | // it under the terms of the GNU General Public License version 2 as | |
6 | // published by the Free Software Foundation. | |
7 | // | |
8 | // This program is distributed in the hope that it will be useful, | |
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | // GNU General Public License for more details. | |
12 | // | |
13 | // You should have received a copy of the GNU General Public License along | |
14 | // with this program; if not, write to the Free Software Foundation, Inc., | |
15 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
16 | // | |
17 | // In addition to these license terms, the author grants the following | |
18 | // additional rights: | |
19 | // | |
20 | // If you modify this program, or any covered work, by linking or | |
21 | // combining it with the OpenSSL project's OpenSSL library (or a | |
22 | // modified version of that library), containing parts covered by the | |
23 | // terms of the OpenSSL or SSLeay licenses, the author | |
24 | // grants you additional permission to convey the resulting work. | |
25 | // Corresponding Source for a non-source form of such a combination | |
26 | // shall include the source code for the parts of OpenSSL used as well | |
27 | // as that of the covered work. | |
28 | // | |
29 | // You may at your option choose to remove this additional permission from | |
30 | // the work, or from any part of it. | |
31 | // | |
32 | // It is possible to build this program in a way that it loads OpenSSL | |
33 | // libraries at run-time. If doing so, the following notices are required | |
34 | // by the OpenSSL and SSLeay licenses: | |
35 | // | |
36 | // This product includes software developed by the OpenSSL Project | |
37 | // for use in the OpenSSL Toolkit. (http://www.openssl.org/) | |
38 | // | |
39 | // This product includes cryptographic software written by Eric Young | |
40 | // (eay@cryptsoft.com) | |
41 | // | |
42 | // | |
43 | // The most up-to-date version of this program is always available from | |
44 | // http://shellinabox.com | |
45 | // | |
46 | // | |
47 | // Notes: | |
48 | // | |
49 | // The author believes that for the purposes of this license, you meet the | |
50 | // requirements for publishing the source code, if your web server publishes | |
51 | // the source in unmodified form (i.e. with licensing information, comments, | |
52 | // formatting, and identifier names intact). If there are technical reasons | |
53 | // that require you to make changes to the source code when serving the | |
54 | // JavaScript (e.g to remove pre-processor directives from the source), these | |
55 | // changes should be done in a reversible fashion. | |
56 | // | |
57 | // The author does not consider websites that reference this script in | |
58 | // unmodified form, and web servers that serve this script in unmodified form | |
59 | // to be derived works. As such, they are believed to be outside of the | |
60 | // scope of this license and not subject to the rights or restrictions of the | |
61 | // GNU General Public License. | |
62 | // | |
63 | // If in doubt, consult a legal professional familiar with the laws that | |
64 | // apply in your country. | |
65 | ||
c27d0db9 MG |
66 | // #define XHR_UNITIALIZED 0 |
67 | // #define XHR_OPEN 1 | |
68 | // #define XHR_SENT 2 | |
69 | // #define XHR_RECEIVING 3 | |
70 | // #define XHR_LOADED 4 | |
7460295f MG |
71 | |
72 | // IE does not define XMLHttpRequest by default, so we provide a suitable | |
73 | // wrapper. | |
74 | if (typeof XMLHttpRequest == 'undefined') { | |
75 | XMLHttpRequest = function() { | |
76 | try { return new ActiveXObject('Msxml2.XMLHTTP.6.0');} catch (e) { } | |
77 | try { return new ActiveXObject('Msxml2.XMLHTTP.3.0');} catch (e) { } | |
78 | try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { } | |
79 | try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) { } | |
80 | throw new Error(''); | |
81 | }; | |
82 | } | |
83 | ||
84 | function extend(subClass, baseClass) { | |
85 | function inheritance() { } | |
86 | inheritance.prototype = baseClass.prototype; | |
87 | subClass.prototype = new inheritance(); | |
88 | subClass.prototype.constructor = subClass; | |
89 | subClass.prototype.superClass = baseClass.prototype; | |
90 | }; | |
91 | ||
92 | function ShellInABox(url, container) { | |
93 | if (url == undefined) { | |
d1edcc0e | 94 | this.url = document.location.href.replace(/[?#].*/, ''); |
7460295f MG |
95 | } else { |
96 | this.url = url; | |
97 | } | |
d1edcc0e | 98 | if (document.location.hash != '') { |
3240f75b | 99 | var hash = decodeURIComponent(document.location.hash). |
d1edcc0e | 100 | replace(/^#/, ''); |
3240f75b MG |
101 | this.nextUrl = hash.replace(/,.*/, ''); |
102 | this.session = hash.replace(/[^,]*,/, ''); | |
d1edcc0e MG |
103 | } else { |
104 | this.nextUrl = this.url; | |
3240f75b | 105 | this.session = null; |
d1edcc0e | 106 | } |
7460295f MG |
107 | this.pendingKeys = ''; |
108 | this.keysInFlight = false; | |
3240f75b | 109 | this.connected = false; |
7460295f MG |
110 | this.superClass.constructor.call(this, container); |
111 | ||
112 | // We have to initiate the first XMLHttpRequest from a timer. Otherwise, | |
113 | // Chrome never realizes that the page has loaded. | |
114 | setTimeout(function(shellInABox) { | |
115 | return function() { | |
116 | shellInABox.sendRequest(); | |
117 | }; | |
118 | }(this), 1); | |
119 | }; | |
120 | extend(ShellInABox, VT100); | |
121 | ||
122 | ShellInABox.prototype.sessionClosed = function() { | |
11cd1451 MG |
123 | try { |
124 | this.connected = false; | |
125 | if (this.session) { | |
126 | this.session = undefined; | |
127 | if (this.cursorX > 0) { | |
128 | this.vt100('\r\n'); | |
129 | } | |
130 | this.vt100('Session closed.'); | |
7460295f | 131 | } |
11cd1451 MG |
132 | this.showReconnect(true); |
133 | } catch (e) { | |
7460295f | 134 | } |
7460295f MG |
135 | }; |
136 | ||
137 | ShellInABox.prototype.reconnect = function() { | |
138 | this.showReconnect(false); | |
139 | if (!this.session) { | |
d1edcc0e MG |
140 | if (document.location.hash != '') { |
141 | // A shellinaboxd daemon launched from a CGI only allows a single | |
142 | // session. In order to reconnect, we must reload the frame definition | |
143 | // and obtain a new port number. As this is a different origin, we | |
144 | // need to get enclosing page to help us. | |
145 | parent.location = this.nextUrl; | |
7460295f | 146 | } else { |
d1edcc0e MG |
147 | if (this.url != this.nextUrl) { |
148 | document.location.replace(this.nextUrl); | |
149 | } else { | |
150 | this.pendingKeys = ''; | |
151 | this.keysInFlight = false; | |
152 | this.reset(true); | |
153 | this.sendRequest(); | |
154 | } | |
7460295f MG |
155 | } |
156 | } | |
d1edcc0e | 157 | return false; |
7460295f MG |
158 | }; |
159 | ||
160 | ShellInABox.prototype.sendRequest = function(request) { | |
161 | if (request == undefined) { | |
162 | request = new XMLHttpRequest(); | |
163 | } | |
11cd1451 MG |
164 | request.open('POST', this.url + '?', true); |
165 | request.setRequestHeader('Cache-Control', 'no-cache'); | |
7460295f MG |
166 | request.setRequestHeader('Content-Type', |
167 | 'application/x-www-form-urlencoded; charset=utf-8'); | |
11cd1451 MG |
168 | var content = 'width=' + this.terminalWidth + |
169 | '&height=' + this.terminalHeight + | |
170 | (this.session ? '&session=' + | |
171 | encodeURIComponent(this.session) : ''); | |
172 | request.setRequestHeader('Content-Length', content.length); | |
30046882 | 173 | |
7460295f MG |
174 | request.onreadystatechange = function(shellInABox) { |
175 | return function() { | |
176 | try { | |
177 | return shellInABox.onReadyStateChange(request); | |
178 | } catch (e) { | |
179 | shellInABox.sessionClosed(); | |
180 | } | |
181 | } | |
182 | }(this); | |
11cd1451 | 183 | request.send(content); |
7460295f MG |
184 | }; |
185 | ||
186 | ShellInABox.prototype.onReadyStateChange = function(request) { | |
c27d0db9 | 187 | if (request.readyState == 4 /* XHR_LOADED */) { |
7460295f | 188 | if (request.status == 200) { |
3240f75b MG |
189 | this.connected = true; |
190 | var response = eval('(' + request.responseText + ')'); | |
7460295f MG |
191 | if (response.data) { |
192 | this.vt100(response.data); | |
193 | } | |
194 | ||
195 | if (!response.session || | |
196 | this.session && this.session != response.session) { | |
197 | this.sessionClosed(); | |
198 | } else { | |
199 | this.session = response.session; | |
200 | this.sendRequest(request); | |
201 | } | |
202 | } else if (request.status == 0) { | |
203 | // Time Out | |
204 | this.sendRequest(request); | |
205 | } else { | |
206 | this.sessionClosed(); | |
207 | } | |
208 | } | |
209 | }; | |
210 | ||
211 | ShellInABox.prototype.sendKeys = function(keys) { | |
3240f75b MG |
212 | if (!this.connected) { |
213 | return; | |
214 | } | |
7460295f MG |
215 | if (this.keysInFlight || this.session == undefined) { |
216 | this.pendingKeys += keys; | |
217 | } else { | |
218 | this.keysInFlight = true; | |
219 | keys = this.pendingKeys + keys; | |
220 | this.pendingKeys = ''; | |
221 | var request = new XMLHttpRequest(); | |
11cd1451 MG |
222 | request.open('POST', this.url + '?', true); |
223 | request.setRequestHeader('Cache-Control', 'no-cache'); | |
7460295f MG |
224 | request.setRequestHeader('Content-Type', |
225 | 'application/x-www-form-urlencoded; charset=utf-8'); | |
11cd1451 MG |
226 | var content = 'width=' + this.terminalWidth + |
227 | '&height=' + this.terminalHeight + | |
228 | '&session=' +encodeURIComponent(this.session)+ | |
229 | '&keys=' + encodeURIComponent(keys); | |
230 | request.setRequestHeader('Content-Length', content.length); | |
7460295f MG |
231 | request.onreadystatechange = function(shellInABox) { |
232 | return function() { | |
233 | try { | |
234 | return shellInABox.keyPressReadyStateChange(request); | |
235 | } catch (e) { | |
236 | } | |
237 | } | |
238 | }(this); | |
11cd1451 | 239 | request.send(content); |
7460295f MG |
240 | } |
241 | }; | |
242 | ||
243 | ShellInABox.prototype.keyPressReadyStateChange = function(request) { | |
c27d0db9 | 244 | if (request.readyState == 4 /* XHR_LOADED */) { |
7460295f MG |
245 | this.keysInFlight = false; |
246 | if (this.pendingKeys) { | |
247 | this.sendKeys(''); | |
248 | } | |
249 | } | |
250 | }; | |
251 | ||
252 | ShellInABox.prototype.keysPressed = function(ch) { | |
253 | var hex = '0123456789ABCDEF'; | |
254 | var s = ''; | |
255 | for (var i = 0; i < ch.length; i++) { | |
256 | var c = ch.charCodeAt(i); | |
257 | if (c < 128) { | |
258 | s += hex.charAt(c >> 4) + hex.charAt(c & 0xF); | |
259 | } else if (c < 0x800) { | |
260 | s += hex.charAt(0xC + (c >> 10) ) + | |
261 | hex.charAt( (c >> 6) & 0xF ) + | |
262 | hex.charAt(0x8 + ((c >> 4) & 0x3)) + | |
263 | hex.charAt( c & 0xF ); | |
264 | } else if (c < 0x10000) { | |
265 | s += 'E' + | |
266 | hex.charAt( (c >> 12) ) + | |
267 | hex.charAt(0x8 + (c >> 10) & 0x3 ) + | |
268 | hex.charAt( (c >> 6) & 0xF ) + | |
269 | hex.charAt(0x8 + ((c >> 4) & 0x3)) + | |
270 | hex.charAt( c & 0xF ); | |
271 | } else if (c < 0x110000) { | |
272 | s += 'F' + | |
273 | hex.charAt( (c >> 18) ) + | |
274 | hex.charAt(0x8 + (c >> 16) & 0x3 ) + | |
275 | hex.charAt( (c >> 12) & 0xF ) + | |
276 | hex.charAt(0x8 + (c >> 10) & 0x3 ) + | |
277 | hex.charAt( (c >> 6) & 0xF ) + | |
278 | hex.charAt(0x8 + ((c >> 4) & 0x3)) + | |
279 | hex.charAt( c & 0xF ); | |
280 | } | |
281 | } | |
282 | this.sendKeys(s); | |
283 | }; | |
284 | ||
285 | ShellInABox.prototype.resized = function(w, h) { | |
286 | // Do not send a resize request until we are fully initialized. | |
287 | if (this.session) { | |
288 | // sendKeys() always transmits the current terminal size. So, flush all | |
289 | // pending keys. | |
290 | this.sendKeys(''); | |
291 | } | |
292 | }; | |
293 | ||
294 | ShellInABox.prototype.toggleSSL = function() { | |
d1edcc0e MG |
295 | if (document.location.hash != '') { |
296 | if (this.nextUrl.match(/\?plain$/)) { | |
297 | this.nextUrl = this.nextUrl.replace(/\?plain$/, ''); | |
298 | } else { | |
299 | this.nextUrl = this.nextUrl.replace(/[?#].*/, '') + '?plain'; | |
300 | } | |
301 | if (!this.session) { | |
302 | parent.location = this.nextUrl; | |
303 | } | |
304 | } else { | |
305 | this.nextUrl = this.nextUrl.match(/^https:/) | |
7460295f MG |
306 | ? this.nextUrl.replace(/^https:/, 'http:').replace(/\/*$/, '/plain') |
307 | : this.nextUrl.replace(/^http/, 'https').replace(/\/*plain$/, ''); | |
d1edcc0e | 308 | } |
7460295f | 309 | if (this.nextUrl.match(/^[:]*:\/\/[^/]*$/)) { |
d1edcc0e | 310 | this.nextUrl += '/'; |
7460295f MG |
311 | } |
312 | if (this.session && this.nextUrl != this.url) { | |
313 | alert('This change will take effect the next time you login.'); | |
314 | } | |
315 | }; | |
316 | ||
317 | ShellInABox.prototype.extendContextMenu = function(entries, actions) { | |
318 | // Modify the entries and actions in place, adding any locally defined | |
319 | // menu entries. | |
320 | var oldActions = [ ]; | |
321 | for (var i = 0; i < actions.length; i++) { | |
322 | oldActions[i] = actions[i]; | |
323 | } | |
324 | for (var node = entries.firstChild, i = 0, j = 0; node; | |
325 | node = node.nextSibling) { | |
326 | if (node.tagName == 'LI') { | |
327 | actions[i++] = oldActions[j++]; | |
328 | if (node.id == "endconfig") { | |
329 | node.id = ''; | |
b624088c MG |
330 | if (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL && |
331 | !(typeof disableSSLMenu != 'undefined' && disableSSLMenu)) { | |
7460295f MG |
332 | // If the server supports both SSL and plain text connections, |
333 | // provide a menu entry to switch between the two. | |
334 | var newNode = document.createElement('li'); | |
d1edcc0e MG |
335 | var isSecure; |
336 | if (document.location.href != '') { | |
337 | isSecure = !this.nextUrl.match(/\?plain$/); | |
338 | } else { | |
339 | isSecure = this.nextUrl.match(/^https:/); | |
340 | } | |
341 | newNode.innerHTML = (isSecure ? '✔ ' : '') + 'Secure'; | |
7460295f MG |
342 | if (node.nextSibling) { |
343 | entries.insertBefore(newNode, node.nextSibling); | |
344 | } else { | |
345 | entries.appendChild(newNode); | |
346 | } | |
347 | actions[i++] = this.toggleSSL; | |
348 | node = newNode; | |
349 | } | |
350 | node.id = 'endconfig'; | |
351 | } | |
352 | } | |
353 | } | |
354 | ||
355 | }; | |
356 | ||
357 | ShellInABox.prototype.about = function() { | |
2532174a | 358 | alert("Shell In A Box version " + "2.9 (revision 147)" + |
e0bb8a33 | 359 | "\nCopyright 2008-2009 by Markus Gutschke\n" + |
7460295f | 360 | "For more information check http://shellinabox.com" + |
46f2036a | 361 | (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ? |
7460295f MG |
362 | "\n\n" + |
363 | "This product includes software developed by the OpenSSL Project\n" + | |
364 | "for use in the OpenSSL Toolkit. (http://www.openssl.org/)\n" + | |
365 | "\n" + | |
366 | "This product includes cryptographic software written by " + | |
367 | "Eric Young\n(eay@cryptsoft.com)" : | |
368 | "")); | |
369 | }; | |
370 |