aboutsummaryrefslogtreecommitdiff
path: root/third_party/websockify/include/wstelnet.js
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/websockify/include/wstelnet.js')
-rw-r--r--third_party/websockify/include/wstelnet.js335
1 files changed, 335 insertions, 0 deletions
diff --git a/third_party/websockify/include/wstelnet.js b/third_party/websockify/include/wstelnet.js
new file mode 100644
index 00000000..fca45f3c
--- /dev/null
+++ b/third_party/websockify/include/wstelnet.js
@@ -0,0 +1,335 @@
+/*
+ * WebSockets telnet client
+ * Copyright (C) 2011 Joel Martin
+ * Licensed under LGPL-3 (see LICENSE.txt)
+ *
+ * Includes VT100.js from:
+ * http://code.google.com/p/sshconsole
+ * Which was modified from:
+ * http://fzort.org/bi/o.php#vt100_js
+ *
+ * Telnet protocol:
+ * http://www.networksorcery.com/enp/protocol/telnet.htm
+ * http://www.networksorcery.com/enp/rfc/rfc1091.txt
+ *
+ * ANSI escape sequeneces:
+ * http://en.wikipedia.org/wiki/ANSI_escape_code
+ * http://ascii-table.com/ansi-escape-sequences-vt-100.php
+ * http://www.termsys.demon.co.uk/vtansi.htm
+ * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+ *
+ * ASCII codes:
+ * http://en.wikipedia.org/wiki/ASCII
+ * http://www.hobbyprojects.com/ascii-table/ascii-table.html
+ *
+ * Other web consoles:
+ * http://stackoverflow.com/questions/244750/ajax-console-window-with-ansi-vt100-support
+ */
+
+
+
+
+function Telnet(target, connect_callback, disconnect_callback) {
+
+var that = {}, // Public API interface
+ vt100, ws, sQ = [];
+ termType = "VT100";
+
+
+Array.prototype.pushStr = function (str) {
+ var n = str.length;
+ for (var i=0; i < n; i++) {
+ this.push(str.charCodeAt(i));
+ }
+}
+
+function do_send() {
+ if (sQ.length > 0) {
+ Util.Debug("Sending " + sQ);
+ ws.send(sQ);
+ sQ = [];
+ }
+}
+
+function do_recv() {
+ //console.log(">> do_recv");
+ var arr = ws.rQshiftBytes(ws.rQlen()), str = "",
+ chr, cmd, code, value;
+
+ Util.Debug("Received array '" + arr + "'");
+ while (arr.length > 0) {
+ chr = arr.shift();
+ switch (chr) {
+ case 255: // IAC
+ cmd = chr;
+ code = arr.shift();
+ value = arr.shift();
+ switch (code) {
+ case 254: // DONT
+ Util.Debug("Got Cmd DONT '" + value + "', ignoring");
+ break;
+ case 253: // DO
+ Util.Debug("Got Cmd DO '" + value + "'");
+ if (value === 24) {
+ // Terminal type
+ Util.Info("Send WILL '" + value + "' (TERM-TYPE)");
+ sQ.push(255, 251, value);
+ } else {
+ // Refuse other DO requests with a WONT
+ Util.Debug("Send WONT '" + value + "'");
+ sQ.push(255, 252, value);
+ }
+ break;
+ case 252: // WONT
+ Util.Debug("Got Cmd WONT '" + value + "', ignoring");
+ break;
+ case 251: // WILL
+ Util.Debug("Got Cmd WILL '" + value + "'");
+ if (value === 1) {
+ // Server will echo, turn off local echo
+ vt100.noecho();
+ // Affirm echo with DO
+ Util.Info("Send Cmd DO '" + value + "' (echo)");
+ sQ.push(255, 253, value);
+ } else {
+ // Reject other WILL offers with a DONT
+ Util.Debug("Send Cmd DONT '" + value + "'");
+ sQ.push(255, 254, value);
+ }
+ break;
+ case 250: // SB (subnegotiation)
+ if (value === 24) {
+ Util.Info("Got IAC SB TERM-TYPE SEND(1) IAC SE");
+ // TERM-TYPE subnegotiation
+ if (arr[0] === 1 &&
+ arr[1] === 255 &&
+ arr[2] === 240) {
+ arr.shift(); arr.shift(); arr.shift();
+ Util.Info("Send IAC SB TERM-TYPE IS(0) '" +
+ termType + "' IAC SE");
+ sQ.push(255, 250, 24, 0);
+ sQ.pushStr(termType);
+ sQ.push(255, 240);
+ } else {
+ Util.Info("Invalid subnegotiation received" + arr);
+ }
+ } else {
+ Util.Info("Ignoring SB " + value);
+ }
+ break;
+ default:
+ Util.Info("Got Cmd " + cmd + " " + value + ", ignoring"); }
+ continue;
+ case 242: // Data Mark (Synch)
+ cmd = chr;
+ code = arr.shift();
+ value = arr.shift();
+ Util.Info("Ignoring Data Mark (Synch)");
+ break;
+ default: // everything else
+ str += String.fromCharCode(chr);
+ }
+ }
+
+ if (sQ) {
+ do_send();
+ }
+
+ if (str) {
+ vt100.write(str);
+ }
+
+ //console.log("<< do_recv");
+}
+
+
+
+that.connect = function(host, port, encrypt) {
+ var host = host,
+ port = port,
+ scheme = "ws://", uri;
+
+ Util.Debug(">> connect");
+ if ((!host) || (!port)) {
+ console.log("must set host and port");
+ return;
+ }
+
+ if (ws) {
+ ws.close();
+ }
+
+ if (encrypt) {
+ scheme = "wss://";
+ }
+ uri = scheme + host + ":" + port;
+ Util.Info("connecting to " + uri);
+
+ ws.open(uri);
+
+ Util.Debug("<< connect");
+}
+
+that.disconnect = function() {
+ Util.Debug(">> disconnect");
+ if (ws) {
+ ws.close();
+ }
+ vt100.curs_set(true, false);
+
+ disconnect_callback();
+ Util.Debug("<< disconnect");
+}
+
+
+function constructor() {
+ /* Initialize Websock object */
+ ws = new Websock();
+
+ ws.on('message', do_recv);
+ ws.on('open', function(e) {
+ Util.Info(">> WebSockets.onopen");
+ vt100.curs_set(true, true);
+ connect_callback();
+ Util.Info("<< WebSockets.onopen");
+ });
+ ws.on('close', function(e) {
+ Util.Info(">> WebSockets.onclose");
+ that.disconnect();
+ Util.Info("<< WebSockets.onclose");
+ });
+ ws.on('error', function(e) {
+ Util.Info(">> WebSockets.onerror");
+ that.disconnect();
+ Util.Info("<< WebSockets.onerror");
+ });
+
+ /* Initialize the terminal emulator/renderer */
+
+ vt100 = new VT100(80, 24, target);
+
+
+ /*
+ * Override VT100 I/O routines
+ */
+
+ // Set handler for sending characters
+ vt100.getch(
+ function send_chr(chr, vt) {
+ var i;
+ Util.Debug(">> send_chr: " + chr);
+ for (i = 0; i < chr.length; i++) {
+ sQ.push(chr.charCodeAt(i));
+ }
+ do_send();
+ vt100.getch(send_chr);
+ }
+ );
+
+ vt100.debug = function(message) {
+ Util.Debug(message + "\n");
+ }
+
+ vt100.warn = function(message) {
+ Util.Warn(message + "\n");
+ }
+
+ vt100.curs_set = function(vis, grab, eventist)
+ {
+ this.debug("curs_set:: vis: " + vis + ", grab: " + grab);
+ if (vis !== undefined)
+ this.cursor_vis_ = (vis > 0);
+ if (eventist === undefined)
+ eventist = window;
+ if (grab === true || grab === false) {
+ if (grab === this.grab_events_)
+ return;
+ if (grab) {
+ this.grab_events_ = true;
+ VT100.the_vt_ = this;
+ Util.addEvent(eventist, 'keydown', vt100.key_down);
+ Util.addEvent(eventist, 'keyup', vt100.key_up);
+ } else {
+ Util.removeEvent(eventist, 'keydown', vt100.key_down);
+ Util.removeEvent(eventist, 'keyup', vt100.key_up);
+ this.grab_events_ = false;
+ VT100.the_vt_ = undefined;
+ }
+ }
+ }
+
+ vt100.key_down = function(e) {
+ var vt = VT100.the_vt_, keysym, ch, str = "";
+
+ if (vt === undefined)
+ return true;
+
+ keysym = getKeysym(e);
+
+ if (keysym < 128) {
+ if (e.ctrlKey) {
+ if (keysym == 64) {
+ // control 0
+ ch = 0;
+ } else if ((keysym >= 97) && (keysym <= 122)) {
+ // control codes 1-26
+ ch = keysym - 96;
+ } else if ((keysym >= 91) && (keysym <= 95)) {
+ // control codes 27-31
+ ch = keysym - 64;
+ } else {
+ Util.Info("Debug unknown control keysym: " + keysym);
+ }
+ } else {
+ ch = keysym;
+ }
+ str = String.fromCharCode(ch);
+ } else {
+ switch (keysym) {
+ case 65505: // Shift, do not send directly
+ break;
+ case 65507: // Ctrl, do not send directly
+ break;
+ case 65293: // Carriage return, line feed
+ str = '\n'; break;
+ case 65288: // Backspace
+ str = '\b'; break;
+ case 65307: // Escape
+ str = '\x1b'; break;
+ case 65361: // Left arrow
+ str = '\x1b[D'; break;
+ case 65362: // Up arrow
+ str = '\x1b[A'; break;
+ case 65363: // Right arrow
+ str = '\x1b[C'; break;
+ case 65364: // Down arrow
+ str = '\x1b[B'; break;
+ default:
+ Util.Info("Unrecoginized keysym " + keysym);
+ }
+ }
+
+ if (str) {
+ vt.key_buf_.push(str);
+ setTimeout(VT100.go_getch_, 0);
+ }
+
+ Util.stopEvent(e);
+ return false;
+ }
+
+ vt100.key_up = function(e) {
+ var vt = VT100.the_vt_;
+ if (vt === undefined)
+ return true;
+ Util.stopEvent(e);
+ return false;
+ }
+
+
+ return that;
+}
+
+return constructor(); // Return the public API interface
+
+} // End of Telnet()