]>
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) { | |
3bdb4585 | 94 | this.rooturl = document.location.href; |
d1edcc0e | 95 | this.url = document.location.href.replace(/[?#].*/, ''); |
7460295f | 96 | } else { |
3bdb4585 | 97 | this.rooturl = url; |
7460295f MG |
98 | this.url = url; |
99 | } | |
d1edcc0e | 100 | if (document.location.hash != '') { |
3240f75b | 101 | var hash = decodeURIComponent(document.location.hash). |
d1edcc0e | 102 | replace(/^#/, ''); |
3240f75b MG |
103 | this.nextUrl = hash.replace(/,.*/, ''); |
104 | this.session = hash.replace(/[^,]*,/, ''); | |
d1edcc0e MG |
105 | } else { |
106 | this.nextUrl = this.url; | |
3240f75b | 107 | this.session = null; |
d1edcc0e | 108 | } |
7460295f MG |
109 | this.pendingKeys = ''; |
110 | this.keysInFlight = false; | |
3240f75b | 111 | this.connected = false; |
7460295f MG |
112 | this.superClass.constructor.call(this, container); |
113 | ||
114 | // We have to initiate the first XMLHttpRequest from a timer. Otherwise, | |
115 | // Chrome never realizes that the page has loaded. | |
116 | setTimeout(function(shellInABox) { | |
117 | return function() { | |
118 | shellInABox.sendRequest(); | |
119 | }; | |
120 | }(this), 1); | |
121 | }; | |
122 | extend(ShellInABox, VT100); | |
123 | ||
124 | ShellInABox.prototype.sessionClosed = function() { | |
11cd1451 MG |
125 | try { |
126 | this.connected = false; | |
127 | if (this.session) { | |
128 | this.session = undefined; | |
129 | if (this.cursorX > 0) { | |
130 | this.vt100('\r\n'); | |
131 | } | |
132 | this.vt100('Session closed.'); | |
7460295f | 133 | } |
11cd1451 MG |
134 | this.showReconnect(true); |
135 | } catch (e) { | |
7460295f | 136 | } |
7460295f MG |
137 | }; |
138 | ||
139 | ShellInABox.prototype.reconnect = function() { | |
140 | this.showReconnect(false); | |
141 | if (!this.session) { | |
d1edcc0e MG |
142 | if (document.location.hash != '') { |
143 | // A shellinaboxd daemon launched from a CGI only allows a single | |
144 | // session. In order to reconnect, we must reload the frame definition | |
145 | // and obtain a new port number. As this is a different origin, we | |
146 | // need to get enclosing page to help us. | |
147 | parent.location = this.nextUrl; | |
7460295f | 148 | } else { |
d1edcc0e MG |
149 | if (this.url != this.nextUrl) { |
150 | document.location.replace(this.nextUrl); | |
151 | } else { | |
152 | this.pendingKeys = ''; | |
153 | this.keysInFlight = false; | |
154 | this.reset(true); | |
155 | this.sendRequest(); | |
156 | } | |
7460295f MG |
157 | } |
158 | } | |
d1edcc0e | 159 | return false; |
7460295f MG |
160 | }; |
161 | ||
162 | ShellInABox.prototype.sendRequest = function(request) { | |
163 | if (request == undefined) { | |
164 | request = new XMLHttpRequest(); | |
165 | } | |
11cd1451 MG |
166 | request.open('POST', this.url + '?', true); |
167 | request.setRequestHeader('Cache-Control', 'no-cache'); | |
7460295f MG |
168 | request.setRequestHeader('Content-Type', |
169 | 'application/x-www-form-urlencoded; charset=utf-8'); | |
11cd1451 MG |
170 | var content = 'width=' + this.terminalWidth + |
171 | '&height=' + this.terminalHeight + | |
172 | (this.session ? '&session=' + | |
3bdb4585 MG |
173 | encodeURIComponent(this.session) : '&rooturl='+ |
174 | encodeURIComponent(this.rooturl)); | |
11cd1451 | 175 | request.setRequestHeader('Content-Length', content.length); |
30046882 | 176 | |
7460295f MG |
177 | request.onreadystatechange = function(shellInABox) { |
178 | return function() { | |
179 | try { | |
180 | return shellInABox.onReadyStateChange(request); | |
181 | } catch (e) { | |
182 | shellInABox.sessionClosed(); | |
183 | } | |
184 | } | |
185 | }(this); | |
11cd1451 | 186 | request.send(content); |
7460295f MG |
187 | }; |
188 | ||
189 | ShellInABox.prototype.onReadyStateChange = function(request) { | |
c27d0db9 | 190 | if (request.readyState == 4 /* XHR_LOADED */) { |
7460295f | 191 | if (request.status == 200) { |
3240f75b MG |
192 | this.connected = true; |
193 | var response = eval('(' + request.responseText + ')'); | |
7460295f MG |
194 | if (response.data) { |
195 | this.vt100(response.data); | |
196 | } | |
197 | ||
198 | if (!response.session || | |
199 | this.session && this.session != response.session) { | |
200 | this.sessionClosed(); | |
201 | } else { | |
202 | this.session = response.session; | |
203 | this.sendRequest(request); | |
204 | } | |
205 | } else if (request.status == 0) { | |
206 | // Time Out | |
207 | this.sendRequest(request); | |
208 | } else { | |
209 | this.sessionClosed(); | |
210 | } | |
211 | } | |
212 | }; | |
213 | ||
214 | ShellInABox.prototype.sendKeys = function(keys) { | |
3240f75b MG |
215 | if (!this.connected) { |
216 | return; | |
217 | } | |
7460295f MG |
218 | if (this.keysInFlight || this.session == undefined) { |
219 | this.pendingKeys += keys; | |
220 | } else { | |
221 | this.keysInFlight = true; | |
222 | keys = this.pendingKeys + keys; | |
223 | this.pendingKeys = ''; | |
224 | var request = new XMLHttpRequest(); | |
11cd1451 MG |
225 | request.open('POST', this.url + '?', true); |
226 | request.setRequestHeader('Cache-Control', 'no-cache'); | |
7460295f MG |
227 | request.setRequestHeader('Content-Type', |
228 | 'application/x-www-form-urlencoded; charset=utf-8'); | |
11cd1451 MG |
229 | var content = 'width=' + this.terminalWidth + |
230 | '&height=' + this.terminalHeight + | |
231 | '&session=' +encodeURIComponent(this.session)+ | |
232 | '&keys=' + encodeURIComponent(keys); | |
233 | request.setRequestHeader('Content-Length', content.length); | |
7460295f MG |
234 | request.onreadystatechange = function(shellInABox) { |
235 | return function() { | |
236 | try { | |
237 | return shellInABox.keyPressReadyStateChange(request); | |
238 | } catch (e) { | |
239 | } | |
240 | } | |
241 | }(this); | |
11cd1451 | 242 | request.send(content); |
7460295f MG |
243 | } |
244 | }; | |
245 | ||
246 | ShellInABox.prototype.keyPressReadyStateChange = function(request) { | |
c27d0db9 | 247 | if (request.readyState == 4 /* XHR_LOADED */) { |
7460295f MG |
248 | this.keysInFlight = false; |
249 | if (this.pendingKeys) { | |
250 | this.sendKeys(''); | |
251 | } | |
252 | } | |
253 | }; | |
254 | ||
255 | ShellInABox.prototype.keysPressed = function(ch) { | |
256 | var hex = '0123456789ABCDEF'; | |
257 | var s = ''; | |
258 | for (var i = 0; i < ch.length; i++) { | |
259 | var c = ch.charCodeAt(i); | |
260 | if (c < 128) { | |
261 | s += hex.charAt(c >> 4) + hex.charAt(c & 0xF); | |
262 | } else if (c < 0x800) { | |
263 | s += hex.charAt(0xC + (c >> 10) ) + | |
264 | hex.charAt( (c >> 6) & 0xF ) + | |
265 | hex.charAt(0x8 + ((c >> 4) & 0x3)) + | |
266 | hex.charAt( c & 0xF ); | |
267 | } else if (c < 0x10000) { | |
268 | s += 'E' + | |
269 | hex.charAt( (c >> 12) ) + | |
270 | hex.charAt(0x8 + (c >> 10) & 0x3 ) + | |
271 | hex.charAt( (c >> 6) & 0xF ) + | |
272 | hex.charAt(0x8 + ((c >> 4) & 0x3)) + | |
273 | hex.charAt( c & 0xF ); | |
274 | } else if (c < 0x110000) { | |
275 | s += 'F' + | |
276 | hex.charAt( (c >> 18) ) + | |
277 | hex.charAt(0x8 + (c >> 16) & 0x3 ) + | |
278 | hex.charAt( (c >> 12) & 0xF ) + | |
279 | hex.charAt(0x8 + (c >> 10) & 0x3 ) + | |
280 | hex.charAt( (c >> 6) & 0xF ) + | |
281 | hex.charAt(0x8 + ((c >> 4) & 0x3)) + | |
282 | hex.charAt( c & 0xF ); | |
283 | } | |
284 | } | |
285 | this.sendKeys(s); | |
286 | }; | |
287 | ||
288 | ShellInABox.prototype.resized = function(w, h) { | |
289 | // Do not send a resize request until we are fully initialized. | |
290 | if (this.session) { | |
291 | // sendKeys() always transmits the current terminal size. So, flush all | |
292 | // pending keys. | |
293 | this.sendKeys(''); | |
294 | } | |
295 | }; | |
296 | ||
297 | ShellInABox.prototype.toggleSSL = function() { | |
d1edcc0e MG |
298 | if (document.location.hash != '') { |
299 | if (this.nextUrl.match(/\?plain$/)) { | |
300 | this.nextUrl = this.nextUrl.replace(/\?plain$/, ''); | |
301 | } else { | |
302 | this.nextUrl = this.nextUrl.replace(/[?#].*/, '') + '?plain'; | |
303 | } | |
304 | if (!this.session) { | |
305 | parent.location = this.nextUrl; | |
306 | } | |
307 | } else { | |
308 | this.nextUrl = this.nextUrl.match(/^https:/) | |
7460295f MG |
309 | ? this.nextUrl.replace(/^https:/, 'http:').replace(/\/*$/, '/plain') |
310 | : this.nextUrl.replace(/^http/, 'https').replace(/\/*plain$/, ''); | |
d1edcc0e | 311 | } |
7460295f | 312 | if (this.nextUrl.match(/^[:]*:\/\/[^/]*$/)) { |
d1edcc0e | 313 | this.nextUrl += '/'; |
7460295f MG |
314 | } |
315 | if (this.session && this.nextUrl != this.url) { | |
316 | alert('This change will take effect the next time you login.'); | |
317 | } | |
318 | }; | |
319 | ||
320 | ShellInABox.prototype.extendContextMenu = function(entries, actions) { | |
321 | // Modify the entries and actions in place, adding any locally defined | |
322 | // menu entries. | |
323 | var oldActions = [ ]; | |
324 | for (var i = 0; i < actions.length; i++) { | |
325 | oldActions[i] = actions[i]; | |
326 | } | |
327 | for (var node = entries.firstChild, i = 0, j = 0; node; | |
328 | node = node.nextSibling) { | |
329 | if (node.tagName == 'LI') { | |
330 | actions[i++] = oldActions[j++]; | |
331 | if (node.id == "endconfig") { | |
332 | node.id = ''; | |
b624088c MG |
333 | if (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL && |
334 | !(typeof disableSSLMenu != 'undefined' && disableSSLMenu)) { | |
7460295f MG |
335 | // If the server supports both SSL and plain text connections, |
336 | // provide a menu entry to switch between the two. | |
337 | var newNode = document.createElement('li'); | |
d1edcc0e | 338 | var isSecure; |
1e1c8024 | 339 | if (document.location.hash != '') { |
d1edcc0e MG |
340 | isSecure = !this.nextUrl.match(/\?plain$/); |
341 | } else { | |
342 | isSecure = this.nextUrl.match(/^https:/); | |
343 | } | |
344 | newNode.innerHTML = (isSecure ? '✔ ' : '') + 'Secure'; | |
7460295f MG |
345 | if (node.nextSibling) { |
346 | entries.insertBefore(newNode, node.nextSibling); | |
347 | } else { | |
348 | entries.appendChild(newNode); | |
349 | } | |
350 | actions[i++] = this.toggleSSL; | |
351 | node = newNode; | |
352 | } | |
353 | node.id = 'endconfig'; | |
354 | } | |
355 | } | |
356 | } | |
357 | ||
358 | }; | |
359 | ||
360 | ShellInABox.prototype.about = function() { | |
c3c8f9e3 | 361 | alert("Shell In A Box version " + "2.10 (revision 190)" + |
e0bb8a33 | 362 | "\nCopyright 2008-2009 by Markus Gutschke\n" + |
7460295f | 363 | "For more information check http://shellinabox.com" + |
46f2036a | 364 | (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ? |
7460295f MG |
365 | "\n\n" + |
366 | "This product includes software developed by the OpenSSL Project\n" + | |
367 | "for use in the OpenSSL Toolkit. (http://www.openssl.org/)\n" + | |
368 | "\n" + | |
369 | "This product includes cryptographic software written by " + | |
370 | "Eric Young\n(eay@cryptsoft.com)" : | |
371 | "")); | |
372 | }; | |
373 |