/* * 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(Base64.encode(sQ)); sQ = []; } } function do_recv(e) { //console.log(">> do_recv"); var arr = Base64.decode(e.data), 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) { // 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 = new WebSocket(uri); ws.onmessage = do_recv; ws.onopen = function(e) { Util.Info(">> WebSockets.onopen"); vt100.curs_set(true, true); connect_callback(); Util.Info("<< WebSockets.onopen"); }; ws.onclose = function(e) { Util.Info(">> WebSockets.onclose"); that.disconnect(); Util.Info("<< WebSockets.onclose"); }; ws.onerror = function(e) { Util.Info(">> WebSockets.onerror"); that.disconnect(); Util.Info("<< WebSockets.onerror"); }; 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 the terminal emulator/renderer */ vt100 = new VT100(80, 24, target); // Turn off local echo vt100.noecho(); /* * 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()