diff options
Diffstat (limited to 'third_party/websockify/include/wstelnet.js')
-rw-r--r-- | third_party/websockify/include/wstelnet.js | 335 |
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() |