]> andersk Git - test.git/blob - shellinabox/shell_in_a_box.js
In an attempt to reduce build dependencies, remove the requirement for
[test.git] / shellinabox / shell_in_a_box.js
1 // ShellInABox.js -- Use XMLHttpRequest to provide an AJAX terminal emulator.
2 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
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
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
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) {
94     this.rooturl    = document.location.href;
95     this.url        = document.location.href.replace(/[?#].*/, '');
96   } else {
97     this.rooturl    = url;
98     this.url        = url;
99   }
100   if (document.location.hash != '') {
101     var hash        = decodeURIComponent(document.location.hash).
102                       replace(/^#/, '');
103     this.nextUrl    = hash.replace(/,.*/, '');
104     this.session    = hash.replace(/[^,]*,/, '');
105   } else {
106     this.nextUrl    = this.url;
107     this.session    = null;
108   }
109   this.pendingKeys  = '';
110   this.keysInFlight = false;
111   this.connected    = false;
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() {
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.');
133     }
134     this.showReconnect(true);
135   } catch (e) {
136   }
137 };
138
139 ShellInABox.prototype.reconnect = function() {
140   this.showReconnect(false);
141   if (!this.session) {
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;
148     } else {
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       }
157     }
158   }
159   return false;
160 };
161
162 ShellInABox.prototype.sendRequest = function(request) {
163   if (request == undefined) {
164     request                  = new XMLHttpRequest();
165   }
166   request.open('POST', this.url + '?', true);
167   request.setRequestHeader('Cache-Control', 'no-cache');
168   request.setRequestHeader('Content-Type',
169                            'application/x-www-form-urlencoded; charset=utf-8');
170   var content                = 'width=' + this.terminalWidth +
171                                '&height=' + this.terminalHeight +
172                                (this.session ? '&session=' +
173                                 encodeURIComponent(this.session) : '&rooturl='+
174                                 encodeURIComponent(this.rooturl));
175   request.setRequestHeader('Content-Length', content.length);
176
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);
186   request.send(content);
187 };
188
189 ShellInABox.prototype.onReadyStateChange = function(request) {
190   if (request.readyState == 4 /* XHR_LOADED */) {
191     if (request.status == 200) {
192       this.connected = true;
193       var response   = eval('(' + request.responseText + ')');
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) {
215   if (!this.connected) {
216     return;
217   }
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();
225     request.open('POST', this.url + '?', true);
226     request.setRequestHeader('Cache-Control', 'no-cache');
227     request.setRequestHeader('Content-Type',
228                            'application/x-www-form-urlencoded; charset=utf-8');
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);
234     request.onreadystatechange = function(shellInABox) {
235       return function() {
236                try {
237                  return shellInABox.keyPressReadyStateChange(request);
238                } catch (e) {
239                }
240              }
241       }(this);
242     request.send(content);
243   }
244 };
245
246 ShellInABox.prototype.keyPressReadyStateChange = function(request) {
247   if (request.readyState == 4 /* XHR_LOADED */) {
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() {
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:/)
309            ? this.nextUrl.replace(/^https:/, 'http:').replace(/\/*$/, '/plain')
310            : this.nextUrl.replace(/^http/, 'https').replace(/\/*plain$/, '');
311   }
312   if (this.nextUrl.match(/^[:]*:\/\/[^/]*$/)) {
313     this.nextUrl     += '/';
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             = '';
333         if (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL &&
334             !(typeof disableSSLMenu != 'undefined' && disableSSLMenu)) {
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');
338           var isSecure;
339           if (document.location.hash != '') {
340             isSecure        = !this.nextUrl.match(/\?plain$/);
341           } else {
342             isSecure        =  this.nextUrl.match(/^https:/);
343           }
344           newNode.innerHTML = (isSecure ? '&#10004; ' : '') + 'Secure';
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() {
361   alert("Shell In A Box version " + "2.10 (revision 230)" +
362         "\nCopyright 2008-2010 by Markus Gutschke\n" +
363         "For more information check http://shellinabox.com" +
364         (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ?
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
This page took 0.055095 seconds and 5 git commands to generate.