1
0
mirror of https://github.com/fcwu/docker-ubuntu-vnc-desktop synced 2026-08-05 16:12:41 +02:00

Push submodule websockify of noVNC

This commit is contained in:
Doro Wu
2015-05-13 10:05:09 +08:00
parent 1bc6fcb092
commit 89a5132f55
79 changed files with 11786 additions and 6 deletions
+919
View File
@@ -0,0 +1,919 @@
// VT100.js -- a text terminal emulator in JavaScript with a ncurses-like
// interface and a POSIX-like interface. (The POSIX-like calls are
// implemented on top of the ncurses-like calls, not the other way round.)
//
// Released under the GNU LGPL v2.1, by Frank Bi <bi@zompower.tk>
//
// 2007-08-12 - refresh():
// - factor out colour code to html_colours_()
// - fix handling of A_REVERSE | A_DIM
// - simplify initial <br /> output code
// - fix underlining colour
// - fix attron() not to turn off attributes
// - decouple A_STANDOUT and A_BOLD
// 2007-08-11 - getch() now calls refresh()
// 2007-08-06 - Safari compat fix -- turn '\r' into '\n' for onkeypress
// 2007-08-05 - Opera compat fixes for onkeypress
// 2007-07-30 - IE compat fixes:
// - change key handling code
// - add <br />...<br />&nbsp; so that 1st and last lines align
// 2007-07-28 - change wrapping behaviour -- writing at the right edge no
// longer causes the cursor to immediately wrap around
// - add <b>...</b> to output to make A_STANDOUT stand out more
// - add handling of backspace, tab, return keys
// - fix doc. of VT100() constructor
// - change from GPL to LGPL
// 2007-07-09 - initial release
//
// class VT100
// A_NORMAL, A_UNDERLINE, A_REVERSE, A_BLINK, A_DIM, A_BOLD, A_STANDOUT
// =class constants=
// Attribute constants.
// VT100(wd, ht, scr_id) =constructor=
// Creates a virtual terminal with width `wd', and
// height `ht'. The terminal will be displayed between
// <pre>...</pre> tags which have element ID `scr_id'.
// addch(ch [, attr])
// Writes out the character `ch'. If `attr' is given,
// it specifies the attributes for the character,
// otherwise the current attributes are used.
// addstr(stuff) Writes out the string `stuff' using the current
// attributes.
// attroff(mode) Turns off any current options given in mode.
// attron(mode) Turns on any options given in mode.
// attrset(mode) Sets the current options to mode.
// bkgdset(attr) Sets the background attributes to attr.
// clear() Clears the terminal using the background attributes,
// and homes the cursor.
// clrtobol() Clears the portion of the terminal from the cursor
// to the bottom.
// clrtoeol() Clears the portion of the current line after the
// cursor.
// curs_set(vis [, grab])
// If `vis' is 0, makes the cursor invisible; otherwise
// make it visible. If `grab' is given and true, starts
// capturing keyboard events (for `getch()'); if given
// and false, stops capturing events.
// echo() Causes key strokes to be automatically echoed on the
// terminal.
// erase() Same as `clear()'.
// getch(isr) Arranges to call `isr' when a key stroke is
// received. The received character and the terminal
// object are passed as arguments to `isr'.
// getmaxyx() Returns an associative array with the maximum row
// (`y') and column (`x') numbers for the terminal.
// getyx() Returns an associative array with the current row
// (`y') and column (`x') of the cursor.
// move(r, c) Moves the cursor to row `r', column `c'.
// noecho() Stops automatically echoing key strokes.
// refresh() Updates the display.
// scroll() Scrolls the terminal up one line.
// standend() Same as `attrset(VT100.A_NORMAL)'.
// standout() Same as `attron(VT100.A_STANDOUT)'.
// write(stuff) Writes `stuff' to the terminal and immediately
// updates the display; (some) escape sequences are
// interpreted and acted on.
// constructor
function VT100(wd, ht, scr_id)
{
var r;
var c;
var scr = document.getElementById(scr_id);
this.wd_ = wd;
this.ht_ = ht;
this.scrolled_ = 0;
this.bkgd_ = {
mode: VT100.A_NORMAL,
fg: VT100.COLOR_WHITE,
bg: VT100.COLOR_BLACK
};
this.c_attr_ = {
mode: VT100.A_NORMAL,
fg: VT100.COLOR_WHITE,
bg: VT100.COLOR_BLACK
};
this.text_ = new Array(ht);
this.attr_ = new Array(ht);
for (r = 0; r < ht; ++r) {
this.text_[r] = new Array(wd);
this.attr_[r] = new Array(wd);
}
this.scr_ = scr;
this.cursor_vis_ = true;
this.grab_events_ = false;
this.getch_isr_ = undefined;
this.key_buf_ = [];
this.echo_ = true;
this.esc_state_ = 0;
// Internal debug setting.
this.debug_ = 0;
this.clear();
this.refresh();
}
// public constants -- colours and colour pairs
VT100.COLOR_BLACK = 0;
VT100.COLOR_BLUE = 1;
VT100.COLOR_GREEN = 2;
VT100.COLOR_CYAN = 3;
VT100.COLOR_RED = 4;
VT100.COLOR_MAGENTA = 5;
VT100.COLOR_YELLOW = 6;
VT100.COLOR_WHITE = 7;
VT100.COLOR_PAIRS = 256;
VT100.COLORS = 8;
// public constants -- attributes
VT100.A_NORMAL = 0;
VT100.A_UNDERLINE = 1;
VT100.A_REVERSE = 2;
VT100.A_BLINK = 4;
VT100.A_DIM = 8;
VT100.A_BOLD = 16;
VT100.A_STANDOUT = 32;
VT100.A_PROTECT = VT100.A_INVIS = 0; // ?
// other public constants
VT100.TABSIZE = 8;
// private constants
VT100.ATTR_FLAGS_ = VT100.A_UNDERLINE | VT100.A_REVERSE | VT100.A_BLINK |
VT100.A_DIM | VT100.A_BOLD | VT100.A_STANDOUT |
VT100.A_PROTECT | VT100.A_INVIS;
VT100.COLOR_SHIFT_ = 6;
VT100.browser_ie_ = (navigator.appName.indexOf("Microsoft") != -1);
VT100.browser_opera_ = (navigator.appName.indexOf("Opera") != -1);
// class variables
VT100.the_vt_ = undefined;
// class methods
// this is actually an event handler
VT100.handle_onkeypress_ = function VT100_handle_onkeypress(event)
{
var vt = VT100.the_vt_, ch;
if (vt === undefined)
return true;
if (VT100.browser_ie_ || VT100.browser_opera_) {
ch = event.keyCode;
if (ch == 13)
ch = 10;
else if (ch > 255 || (ch < 32 && ch != 8))
return true;
ch = String.fromCharCode(ch);
} else {
ch = event.charCode;
//dump("ch: " + ch + "\n");
//dump("ctrl?: " + event.ctrlKey + "\n");
vt.debug("onkeypress:: keyCode: " + event.keyCode + ", ch: " + event.charCode);
if (ch) {
if (ch > 255)
return true;
if (event.ctrlKey && event.shiftKey) {
// Don't send the copy/paste commands.
var charStr = String.fromCharCode(ch);
if (charStr == 'C' || charStr == 'V') {
return false;
}
}
if (event.ctrlKey) {
ch = String.fromCharCode(ch - 96);
} else {
ch = String.fromCharCode(ch);
if (ch == '\r')
ch = '\n';
}
} else {
switch (event.keyCode) {
case event.DOM_VK_BACK_SPACE:
ch = '\b';
break;
case event.DOM_VK_TAB:
ch = '\t';
// Stop tab from moving to another element.
event.preventDefault();
break;
case event.DOM_VK_RETURN:
case event.DOM_VK_ENTER:
ch = '\n';
break;
case event.DOM_VK_UP:
ch = '\x1b[A';
break;
case event.DOM_VK_DOWN:
ch = '\x1b[B';
break;
case event.DOM_VK_RIGHT:
ch = '\x1b[C';
break;
case event.DOM_VK_LEFT:
ch = '\x1b[D';
break;
case event.DOM_VK_DELETE:
ch = '\x1b[3~';
break;
case event.DOM_VK_HOME:
ch = '\x1b[H';
break;
case event.DOM_VK_ESCAPE:
ch = '\x1bc';
break;
default:
return true;
}
}
}
vt.key_buf_.push(ch);
setTimeout(VT100.go_getch_, 0);
return false;
}
// this is actually an event handler
VT100.handle_onkeydown_ = function VT100_handle_onkeydown()
{
var vt = VT100.the_vt_, ch;
switch (event.keyCode) {
case 8:
ch = '\b'; break;
default:
return true;
}
vt.key_buf_.push(ch);
setTimeout(VT100.go_getch_, 0);
return false;
}
VT100.go_getch_ = function VT100_go_getch()
{
var vt = VT100.the_vt_;
if (vt === undefined)
return;
var isr = vt.getch_isr_;
vt.getch_isr_ = undefined;
if (isr === undefined)
return;
var ch = vt.key_buf_.shift();
if (ch === undefined) {
vt.getch_isr_ = isr;
return;
}
if (vt.echo_)
vt.addch(ch);
isr(ch, vt);
}
// object methods
VT100.prototype.may_scroll_ = function()
{
var ht = this.ht_, cr = this.row_;
while (cr >= ht) {
this.scroll();
--cr;
}
this.row_ = cr;
}
VT100.prototype.html_colours_ = function(attr)
{
var fg, bg, co0, co1;
fg = attr.fg;
bg = attr.bg;
switch (attr.mode & (VT100.A_REVERSE | VT100.A_DIM | VT100.A_BOLD)) {
case 0:
case VT100.A_DIM | VT100.A_BOLD:
co0 = '00'; co1 = 'c0';
break;
case VT100.A_BOLD:
co0 = '00'; co1 = 'ff';
break;
case VT100.A_DIM:
if (fg == VT100.COLOR_BLACK)
co0 = '40';
else
co0 = '00';
co1 = '40';
break;
case VT100.A_REVERSE:
case VT100.A_REVERSE | VT100.A_DIM | VT100.A_BOLD:
co0 = 'c0'; co1 = '40';
break;
case VT100.A_REVERSE | VT100.A_BOLD:
co0 = 'c0'; co1 = '00';
break;
default:
if (fg == VT100.COLOR_BLACK)
co0 = '80';
else
co0 = 'c0';
co1 = 'c0';
}
return {
f: '#' + (fg & 4 ? co1 : co0) +
(fg & 2 ? co1 : co0) +
(fg & 1 ? co1 : co0),
b: '#' + (bg & 4 ? co1 : co0) +
(bg & 2 ? co1 : co0) +
(bg & 1 ? co1 : co0)
};
}
VT100.prototype.addch = function(ch, attr)
{
var cc = this.col_;
this.debug("addch:: ch: " + ch + ", attr: " + attr);
switch (ch) {
case '\b':
if (cc != 0)
--cc;
break;
case '\n':
++this.row_;
cc = 0;
this.clrtoeol();
this.may_scroll_();
break;
case '\r':
this.may_scroll_();
cc = 0;
break;
case '\t':
this.may_scroll_();
cc += VT100.TABSIZE - cc % VT100.TABSIZE;
if (cc >= this.wd_) {
++this.row_;
cc -= this.wd_;
}
break;
default:
if (attr === undefined)
attr = this._cloneAttr(this.c_attr_);
if (cc >= this.wd_) {
++this.row_;
cc = 0;
}
this.may_scroll_();
this.text_[this.row_][cc] = ch;
this.attr_[this.row_][cc] = attr;
++cc;
}
this.col_ = cc;
}
VT100.prototype.addstr = function(stuff)
{
for (var i = 0; i < stuff.length; ++i)
this.addch(stuff.charAt(i));
}
VT100.prototype._cloneAttr = function VT100_cloneAttr(a)
{
return {
mode: a.mode,
fg: a.fg,
bg: a.bg
};
}
VT100.prototype.attroff = function(a)
{
//dump("attroff: " + a + "\n");
a &= VT100.ATTR_FLAGS_;
this.c_attr_.mode &= ~a;
}
VT100.prototype.attron = function(a)
{
//dump("attron: " + a + "\n");
a &= VT100.ATTR_FLAGS_;
this.c_attr_.mode |= a;
}
VT100.prototype.attrset = function(a)
{
//dump("attrset: " + a + "\n");
this.c_attr_.mode = a;
}
VT100.prototype.fgset = function(fg)
{
//dump("fgset: " + fg + "\n");
this.c_attr_.fg = fg;
}
VT100.prototype.bgset = function(bg)
{
//dump("bgset: " + bg + "\n");
if (bg !== 0) {
this.warn("bgset: " + bg + "\n");
}
this.c_attr_.bg = bg;
}
VT100.prototype.bkgdset = function(a)
{
this.bkgd_ = a;
}
VT100.prototype.clear = function()
{
this.debug("clear");
this.row_ = this.col_ = 0;
this.scrolled_ = 0;
for (r = 0; r < this.ht_; ++r) {
for (c = 0; c < this.wd_; ++c) {
this.text_[r][c] = ' ';
this.attr_[r][c] = this._cloneAttr(this.bkgd_);
}
}
}
VT100.prototype.clrtobot = function()
{
this.debug("clrtobot, row: " + this.row_);
var ht = this.ht_;
var wd = this.wd_;
this.clrtoeol();
for (var r = this.row_ + 1; r < ht; ++r) {
for (var c = 0; c < wd; ++c) {
this.text_[r][c] = ' ';
this.attr_[r][c] = this.bkgd_;
}
}
}
VT100.prototype.clrtoeol = function()
{
this.debug("clrtoeol, col: " + this.col_);
var r = this.row_;
if (r >= this.ht_)
return;
for (var c = this.col_; c < this.wd_; ++c) {
this.text_[r][c] = ' ';
this.attr_[r][c] = this.bkgd_;
}
}
VT100.prototype.clearpos = function(row, col)
{
this.debug("clearpos (" + row + ", " + col + ")");
if (row < 0 || row >= this.ht_)
return;
if (col < 0 || col >= this.wd_)
return;
this.text_[row][col] = ' ';
this.attr_[row][col] = this.bkgd_;
}
VT100.prototype.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;
eventist.addEventListener("keypress", VT100.handle_onkeypress_, false);
if (VT100.browser_ie_)
document.onkeydown = VT100.handle_onkeydown_;
} else {
eventist.removeEventListener("keypress", VT100.handle_onkeypress_, false);
if (VT100.browser_ie_)
document.onkeydown = VT100.handle_onkeydown_;
this.grab_events_ = false;
VT100.the_vt_ = undefined;
}
}
}
VT100.prototype.echo = function()
{
this.debug("echo on");
this.echo_ = true;
}
VT100.prototype.erase = VT100.prototype.clear;
VT100.prototype.getch = function(isr)
{
this.debug("getch");
this.refresh();
this.getch_isr_ = isr;
setTimeout(VT100.go_getch_, 0);
}
VT100.prototype.getmaxyx = function()
{
return { y: this.ht_ - 1, x: this.wd_ - 1 };
}
VT100.prototype.getyx = function()
{
return { y: this.row_, x: this.col_ };
}
VT100.prototype.move = function(r, c)
{
this.debug("move: (" + r + ", " + c + ")");
if (r < 0)
r = 0;
else if (r >= this.ht_)
r = this.ht_ - 1;
if (c < 0)
c = 0;
else if (c >= this.wd_)
c = this.wd_ - 1;
this.row_ = r;
this.col_ = c;
}
VT100.prototype.noecho = function()
{
this.debug("echo off");
this.echo_ = false;
}
VT100.prototype.refresh = function()
{
this.debug("refresh");
var r, c, stuff = "", start_tag = "", end_tag = "", at = -1, n_at, ch,
pair, cr, cc, ht, wd, cv, added_end_tag;
ht = this.ht_;
wd = this.wd_;
cr = this.row_;
cc = this.col_;
cv = this.cursor_vis_;
var innerHTML = this.scr_.innerHTML;
if (cc >= wd)
cc = wd - 1;
for (r = 0; r < ht; ++r) {
if (r > 0) {
stuff += '\n';
}
for (c = 0; c < wd; ++c) {
added_end_tag = false;
n_at = this.attr_[r][c];
if (cv && r == cr && c == cc) {
// Draw the cursor here.
n_at = this._cloneAttr(n_at);
n_at.mode ^= VT100.A_REVERSE;
}
// If the attributes changed, make a new span.
if (n_at.mode != at.mode || n_at.fg != at.fg || n_at.bg != at.bg) {
if (c > 0) {
stuff += end_tag;
}
start_tag = "";
end_tag = "";
if (n_at.mode & VT100.A_BLINK) {
start_tag = "<blink>";
end_tag = "</blink>" + end_tag;
}
if (n_at.mode & VT100.A_STANDOUT)
n_at.mode |= VT100.A_BOLD;
pair = this.html_colours_(n_at);
start_tag += '<span style="color:' + pair.f +
';background-color:' + pair.b;
if (n_at.mode & VT100.A_UNDERLINE)
start_tag += ';text-decoration:underline';
start_tag += ';">';
stuff += start_tag;
end_tag = "</span>" + end_tag;
at = n_at;
added_end_tag = true;
} else if (c == 0) {
stuff += start_tag;
}
ch = this.text_[r][c];
switch (ch) {
case '&':
stuff += '&amp;'; break;
case '<':
stuff += '&lt;'; break;
case '>':
stuff += '&gt;'; break;
case ' ':
//stuff += '&nbsp;'; break;
stuff += ' '; break;
default:
stuff += ch;
}
}
if (!added_end_tag)
stuff += end_tag;
}
this.scr_.innerHTML = "<b>" + stuff + "</b>\n";
}
VT100.prototype.scroll = function()
{
this.scrolled_ += 1;
this.debug("scrolled: " + this.scrolled_);
var n_text = this.text_[0], n_attr = this.attr_[0],
ht = this.ht_, wd = this.wd_;
for (var r = 1; r < ht; ++r) {
this.text_[r - 1] = this.text_[r];
this.attr_[r - 1] = this.attr_[r];
}
this.text_[ht - 1] = n_text;
this.attr_[ht - 1] = n_attr;
for (var c = 0; c < wd; ++c) {
n_text[c] = ' ';
n_attr[c] = this.bkgd_;
}
}
VT100.prototype.standend = function()
{
//this.debug("standend");
this.attrset(0);
}
VT100.prototype.standout = function()
{
//this.debug("standout");
this.attron(VT100.A_STANDOUT);
}
VT100.prototype.write = function(stuff)
{
var ch, x, r, c, i, j, yx, myx;
for (i = 0; i < stuff.length; ++i) {
ch = stuff.charAt(i);
if (ch == '\x0D') {
this.debug("write:: ch: " + ch.charCodeAt(0) + ", '\\x0D'");
} else {
this.debug("write:: ch: " + ch.charCodeAt(0) + ", '" + (ch == '\x1b' ? "ESC" : ch) + "'");
}
//dump("ch: " + ch.charCodeAt(0) + ", '" + (ch == '\x1b' ? "ESC" : ch) + "'\n");
switch (ch) {
case '\x00':
case '\x7f':
case '\x07': /* bell, ignore it */
this.debug("write:: ignoring bell character: " + ch);
continue;
case '\a':
case '\b':
case '\t':
case '\r':
this.addch(ch);
continue;
case '\n':
case '\v':
case '\f': // what a mess
yx = this.getyx();
myx = this.getmaxyx();
if (yx.y >= myx.y) {
this.scroll();
this.move(myx.y, 0);
} else
this.move(yx.y + 1, 0);
continue;
case '\x18':
case '\x1a':
this.esc_state_ = 0;
this.debug("write:: set escape state: 0");
continue;
case '\x1b':
this.esc_state_ = 1;
this.debug("write:: set escape state: 1");
continue;
case '\x9b':
this.esc_state_ = 2;
this.debug("write:: set escape state: 2");
continue;
}
// not a recognized control character
switch (this.esc_state_) {
case 0: // not in escape sequence
this.addch(ch);
break;
case 1: // just saw ESC
switch (ch) {
case '[':
this.esc_state_ = 2;
this.debug("write:: set escape state: 2");
break;
case '=':
/* Set keypade mode (ignored) */
this.debug("write:: set keypade mode: ignored");
this.esc_state_ = 0;
break;
case '>':
/* Reset keypade mode (ignored) */
this.debug("write:: reset keypade mode: ignored");
this.esc_state_ = 0;
break;
case 'H':
/* Set tab at cursor column (ignored) */
this.debug("write:: set tab cursor column: ignored");
this.esc_state_ = 0;
break;
}
break;
case 2: // just saw CSI
switch (ch) {
case 'K':
/* Erase in Line */
this.esc_state_ = 0;
this.clrtoeol();
continue;
case 'H':
/* Move to (0,0). */
this.esc_state_ = 0;
this.move(0, 0);
continue;
case 'J':
/* Clear to the bottom. */
this.esc_state_ = 0;
this.clrtobot();
continue;
case '?':
/* Special VT100 mode handling. */
this.esc_state_ = 5;
this.debug("write:: special vt100 mode");
continue;
}
// Drop through to next case.
this.csi_parms_ = [0];
this.debug("write:: set escape state: 3");
this.esc_state_ = 3;
case 3: // saw CSI and parameters
switch (ch) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
x = this.csi_parms_.pop();
this.csi_parms_.push(x * 10 + ch * 1);
this.debug("csi_parms_: " + this.csi_parms_);
continue;
case ';':
if (this.csi_parms_.length < 17)
this.csi_parms_.push(0);
continue;
}
this.esc_state_ = 0;
switch (ch) {
case 'A':
// Cursor Up <ESC>[{COUNT}A
this.move(this.row_ - Math.max(1, this.csi_parms_[0]),
this.col_);
break;
case 'B':
// Cursor Down <ESC>[{COUNT}B
this.move(this.row_ + Math.max(1, this.csi_parms_[0]),
this.col_);
break;
case 'C':
// Cursor Forward <ESC>[{COUNT}C
this.move(this.row_,
this.col_ + Math.max(1, this.csi_parms_[0]));
break;
case 'c':
this.warn("write:: got TERM query");
break;
case 'D':
// Cursor Backward <ESC>[{COUNT}D
this.move(this.row_,
this.col_ - Math.max(1, this.csi_parms_[0]));
break;
case 'f':
case 'H':
// Cursor Home <ESC>[{ROW};{COLUMN}H
this.csi_parms_.push(0);
this.move(this.csi_parms_[0] - 1,
this.csi_parms_[1] - 1);
break;
case 'J':
switch (this.csi_parms_[0]) {
case 0:
this.clrtobot();
break;
case 2:
this.clear();
this.move(0, 0);
}
break;
case 'm':
for (j=0; j<this.csi_parms_.length; ++j) {
x = this.csi_parms_[j];
switch (x) {
case 0:
this.standend();
this.fgset(this.bkgd_.fg);
this.bgset(this.bkgd_.bg);
break;
case 1:
this.attron(VT100.A_BOLD);
break;
case 30:
this.fgset(VT100.COLOR_BLACK);
break;
case 31:
this.fgset(VT100.COLOR_RED);
break;
case 32:
this.fgset(VT100.COLOR_GREEN);
break;
case 33:
this.fgset(VT100.COLOR_YELLOW);
break;
case 34:
this.fgset(VT100.COLOR_BLUE);
break;
case 35:
this.fgset(VT100.COLOR_MAGENTA);
break;
case 36:
this.fgset(VT100.COLOR_CYAN);
break;
case 37:
this.fgset(VT100.COLOR_WHITE);
break;
case 40:
this.bgset(VT100.COLOR_BLACK);
break;
case 41:
this.bgset(VT100.COLOR_RED);
break;
case 42:
this.bgset(VT100.COLOR_GREEN);
break;
case 44:
this.bgset(VT100.COLOR_YELLOW);
break;
case 44:
this.bgset(VT100.COLOR_BLUE);
break;
case 45:
this.bgset(VT100.COLOR_MAGENTA);
break;
case 46:
this.bgset(VT100.COLOR_CYAN);
break;
case 47:
this.bgset(VT100.COLOR_WHITE);
break;
}
}
break;
case 'r':
// 1,24r - set scrolling region (ignored)
break;
case '[':
this.debug("write:: set escape state: 4");
this.esc_state_ = 4;
break;
case 'g':
// 0g: clear tab at cursor (ignored)
// 3g: clear all tabs (ignored)
break;
default:
this.warn("write:: unknown command: " + ch);
this.csi_parms_ = [];
break;
}
break;
case 4: // saw CSI [
this.esc_state_ = 0; // gobble char.
break;
case 5: // Special mode handling, saw <ESC>[?
// Expect a number - the reset type
this.csi_parms_ = [ch];
this.esc_state_ = 6;
break;
case 6: // Reset mode handling, saw <ESC>[?1
// Expect a letter - the mode target, example:
// <ESC>[?1l : cursor key mode = cursor
// <ESC>[?1h : save current screen, create new empty
// screen and position at 0,0
// <ESC>[?5l : White on blk
// XXX: Ignored for now.
//dump("Saw reset mode: <ESC>[?" + this.csi_parms_[0] + ch + "\n");
this.esc_state_ = 0;
this.debug("write:: set escape state: 0");
break;
}
}
this.refresh();
}
VT100.prototype.debug = function(message) {
if (this.debug_) {
dump(message + "\n");
}
}
VT100.prototype.warn = function(message) {
dump(message + "\n");
}
+114
View File
@@ -0,0 +1,114 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js
/*jslint white: false, bitwise: false, plusplus: false */
/*global console */
var Base64 = {
/* Convert data (an array of integers) to a Base64 string. */
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''),
base64Pad : '=',
encode: function (data) {
"use strict";
var result = '';
var toBase64Table = Base64.toBase64Table;
var base64Pad = Base64.base64Pad;
var length = data.length;
var i;
// Convert every three bytes to 4 ascii characters.
/* BEGIN LOOP */
for (i = 0; i < (length - 2); i += 3) {
result += toBase64Table[data[i] >> 2];
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
result += toBase64Table[data[i+2] & 0x3f];
}
/* END LOOP */
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
if (length%3) {
i = length - (length%3);
result += toBase64Table[data[i] >> 2];
if ((length%3) === 2) {
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
result += toBase64Table[(data[i+1] & 0x0f) << 2];
result += base64Pad;
} else {
result += toBase64Table[(data[i] & 0x03) << 4];
result += base64Pad + base64Pad;
}
}
return result;
},
/* Convert Base64 data to a string */
toBinaryTable : [
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
],
decode: function (data, offset) {
"use strict";
offset = typeof(offset) !== 'undefined' ? offset : 0;
var toBinaryTable = Base64.toBinaryTable;
var base64Pad = Base64.base64Pad;
var result, result_length, idx, i, c, padding;
var leftbits = 0; // number of bits decoded, but yet to be appended
var leftdata = 0; // bits decoded, but yet to be appended
var data_length = data.indexOf('=') - offset;
if (data_length < 0) { data_length = data.length - offset; }
/* Every four characters is 3 resulting numbers */
result_length = (data_length >> 2) * 3 + Math.floor((data_length%4)/1.5);
result = new Array(result_length);
// Convert one by one.
/* BEGIN LOOP */
for (idx = 0, i = offset; i < data.length; i++) {
c = toBinaryTable[data.charCodeAt(i) & 0x7f];
padding = (data.charAt(i) === base64Pad);
// Skip illegal characters and whitespace
if (c === -1) {
console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
continue;
}
// Collect data into leftdata, update bitcount
leftdata = (leftdata << 6) | c;
leftbits += 6;
// If we have 8 or more bits, append 8 bits to the result
if (leftbits >= 8) {
leftbits -= 8;
// Append if not padding.
if (!padding) {
result[idx++] = (leftdata >> leftbits) & 0xff;
}
leftdata &= (1 << leftbits) - 1;
}
}
/* END LOOP */
// If there are any bits left, the base64 string was corrupted
if (leftbits) {
throw {name: 'Base64-Error',
message: 'Corrupted base64 string'};
}
return result;
}
}; /* End of Base64 namespace */
+99
View File
@@ -0,0 +1,99 @@
/*
* from noVNC: HTML5 VNC client
* Copyright (C) 2010 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt)
*/
/* Translate DOM key down/up event to keysym value */
function getKeysym(e) {
var evt, keysym;
evt = (e ? e : window.event);
/* Remap modifier and special keys */
switch ( evt.keyCode ) {
case 8 : keysym = 0xFF08; break; // BACKSPACE
case 9 : keysym = 0xFF09; break; // TAB
case 13 : keysym = 0xFF0D; break; // ENTER
case 27 : keysym = 0xFF1B; break; // ESCAPE
case 45 : keysym = 0xFF63; break; // INSERT
case 46 : keysym = 0xFFFF; break; // DELETE
case 36 : keysym = 0xFF50; break; // HOME
case 35 : keysym = 0xFF57; break; // END
case 33 : keysym = 0xFF55; break; // PAGE_UP
case 34 : keysym = 0xFF56; break; // PAGE_DOWN
case 37 : keysym = 0xFF51; break; // LEFT
case 38 : keysym = 0xFF52; break; // UP
case 39 : keysym = 0xFF53; break; // RIGHT
case 40 : keysym = 0xFF54; break; // DOWN
case 112 : keysym = 0xFFBE; break; // F1
case 113 : keysym = 0xFFBF; break; // F2
case 114 : keysym = 0xFFC0; break; // F3
case 115 : keysym = 0xFFC1; break; // F4
case 116 : keysym = 0xFFC2; break; // F5
case 117 : keysym = 0xFFC3; break; // F6
case 118 : keysym = 0xFFC4; break; // F7
case 119 : keysym = 0xFFC5; break; // F8
case 120 : keysym = 0xFFC6; break; // F9
case 121 : keysym = 0xFFC7; break; // F10
case 122 : keysym = 0xFFC8; break; // F11
case 123 : keysym = 0xFFC9; break; // F12
case 16 : keysym = 0xFFE1; break; // SHIFT
case 17 : keysym = 0xFFE3; break; // CONTROL
//case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option)
case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command)
default : keysym = evt.keyCode; break;
}
/* Remap symbols */
switch (keysym) {
case 186 : keysym = 59; break; // ; (IE)
case 187 : keysym = 61; break; // = (IE)
case 188 : keysym = 44; break; // , (Mozilla, IE)
case 109 : // - (Mozilla)
if (Util.Engine.gecko) {
keysym = 45; }
break;
case 189 : keysym = 45; break; // - (IE)
case 190 : keysym = 46; break; // . (Mozilla, IE)
case 191 : keysym = 47; break; // / (Mozilla, IE)
case 192 : keysym = 96; break; // ` (Mozilla, IE)
case 219 : keysym = 91; break; // [ (Mozilla, IE)
case 220 : keysym = 92; break; // \ (Mozilla, IE)
case 221 : keysym = 93; break; // ] (Mozilla, IE)
case 222 : keysym = 39; break; // ' (Mozilla, IE)
}
/* Remap shifted and unshifted keys */
if (!!evt.shiftKey) {
switch (keysym) {
case 48 : keysym = 41 ; break; // ) (shifted 0)
case 49 : keysym = 33 ; break; // ! (shifted 1)
case 50 : keysym = 64 ; break; // @ (shifted 2)
case 51 : keysym = 35 ; break; // # (shifted 3)
case 52 : keysym = 36 ; break; // $ (shifted 4)
case 53 : keysym = 37 ; break; // % (shifted 5)
case 54 : keysym = 94 ; break; // ^ (shifted 6)
case 55 : keysym = 38 ; break; // & (shifted 7)
case 56 : keysym = 42 ; break; // * (shifted 8)
case 57 : keysym = 40 ; break; // ( (shifted 9)
case 59 : keysym = 58 ; break; // : (shifted `)
case 61 : keysym = 43 ; break; // + (shifted ;)
case 44 : keysym = 60 ; break; // < (shifted ,)
case 45 : keysym = 95 ; break; // _ (shifted -)
case 46 : keysym = 62 ; break; // > (shifted .)
case 47 : keysym = 63 ; break; // ? (shifted /)
case 96 : keysym = 126; break; // ~ (shifted `)
case 91 : keysym = 123; break; // { (shifted [)
case 92 : keysym = 124; break; // | (shifted \)
case 93 : keysym = 125; break; // } (shifted ])
case 39 : keysym = 34 ; break; // " (shifted ')
}
} else if ((keysym >= 65) && (keysym <=90)) {
/* Remap unshifted A-Z */
keysym += 32;
}
return keysym;
}
+379
View File
@@ -0,0 +1,379 @@
/*
* from noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
"use strict";
/*jslint bitwise: false, white: false */
/*global window, console, document, navigator, ActiveXObject */
// Globals defined here
var Util = {};
/*
* Make arrays quack
*/
Array.prototype.push8 = function (num) {
this.push(num & 0xFF);
};
Array.prototype.push16 = function (num) {
this.push((num >> 8) & 0xFF,
(num ) & 0xFF );
};
Array.prototype.push32 = function (num) {
this.push((num >> 24) & 0xFF,
(num >> 16) & 0xFF,
(num >> 8) & 0xFF,
(num ) & 0xFF );
};
// IE does not support map (even in IE9)
//This prototype is provided by the Mozilla foundation and
//is distributed under the MIT license.
//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
if (!Array.prototype.map)
{
Array.prototype.map = function(fun /*, thisp*/)
{
var len = this.length;
if (typeof fun != "function")
throw new TypeError();
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this)
res[i] = fun.call(thisp, this[i], i, this);
}
return res;
};
}
//
// requestAnimationFrame shim with setTimeout fallback
//
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
/*
* ------------------------------------------------------
* Namespaced in Util
* ------------------------------------------------------
*/
/*
* Logging/debug routines
*/
Util._log_level = 'warn';
Util.init_logging = function (level) {
if (typeof level === 'undefined') {
level = Util._log_level;
} else {
Util._log_level = level;
}
if (typeof window.console === "undefined") {
if (typeof window.opera !== "undefined") {
window.console = {
'log' : window.opera.postError,
'warn' : window.opera.postError,
'error': window.opera.postError };
} else {
window.console = {
'log' : function(m) {},
'warn' : function(m) {},
'error': function(m) {}};
}
}
Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
switch (level) {
case 'debug': Util.Debug = function (msg) { console.log(msg); };
case 'info': Util.Info = function (msg) { console.log(msg); };
case 'warn': Util.Warn = function (msg) { console.warn(msg); };
case 'error': Util.Error = function (msg) { console.error(msg); };
case 'none':
break;
default:
throw("invalid logging type '" + level + "'");
}
};
Util.get_logging = function () {
return Util._log_level;
};
// Initialize logging level
Util.init_logging();
// Set configuration default for Crockford style function namespaces
Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) {
var getter, setter;
// Default getter function
getter = function (idx) {
if ((type in {'arr':1, 'array':1}) &&
(typeof idx !== 'undefined')) {
return cfg[v][idx];
} else {
return cfg[v];
}
};
// Default setter function
setter = function (val, idx) {
if (type in {'boolean':1, 'bool':1}) {
if ((!val) || (val in {'0':1, 'no':1, 'false':1})) {
val = false;
} else {
val = true;
}
} else if (type in {'integer':1, 'int':1}) {
val = parseInt(val, 10);
} else if (type === 'str') {
val = String(val);
} else if (type === 'func') {
if (!val) {
val = function () {};
}
}
if (typeof idx !== 'undefined') {
cfg[v][idx] = val;
} else {
cfg[v] = val;
}
};
// Set the description
api[v + '_description'] = desc;
// Set the getter function
if (typeof api['get_' + v] === 'undefined') {
api['get_' + v] = getter;
}
// Set the setter function with extra sanity checks
if (typeof api['set_' + v] === 'undefined') {
api['set_' + v] = function (val, idx) {
if (mode in {'RO':1, 'ro':1}) {
throw(v + " is read-only");
} else if ((mode in {'WO':1, 'wo':1}) &&
(typeof cfg[v] !== 'undefined')) {
throw(v + " can only be set once");
}
setter(val, idx);
};
}
// Set the default value
if (typeof defaults[v] !== 'undefined') {
defval = defaults[v];
} else if ((type in {'arr':1, 'array':1}) &&
(! (defval instanceof Array))) {
defval = [];
}
// Coerce existing setting to the right type
//Util.Debug("v: " + v + ", defval: " + defval + ", defaults[v]: " + defaults[v]);
setter(defval);
};
// Set group of configuration defaults
Util.conf_defaults = function(cfg, api, defaults, arr) {
var i;
for (i = 0; i < arr.length; i++) {
Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1],
arr[i][2], arr[i][3], arr[i][4]);
}
};
/*
* Cross-browser routines
*/
// Dynamically load scripts without using document.write()
// Reference: http://unixpapa.com/js/dyna.html
//
// Handles the case where load_scripts is invoked from a script that
// itself is loaded via load_scripts. Once all scripts are loaded the
// window.onscriptsloaded handler is called (if set).
Util.get_include_uri = function() {
return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
}
Util._loading_scripts = [];
Util._pending_scripts = [];
Util.load_scripts = function(files) {
var head = document.getElementsByTagName('head')[0], script,
ls = Util._loading_scripts, ps = Util._pending_scripts;
for (var f=0; f<files.length; f++) {
script = document.createElement('script');
script.type = 'text/javascript';
script.src = Util.get_include_uri() + files[f];
//console.log("loading script: " + script.src);
script.onload = script.onreadystatechange = function (e) {
while (ls.length > 0 && (ls[0].readyState === 'loaded' ||
ls[0].readyState === 'complete')) {
// For IE, append the script to trigger execution
var s = ls.shift();
//console.log("loaded script: " + s.src);
head.appendChild(s);
}
if (!this.readyState ||
(Util.Engine.presto && this.readyState === 'loaded') ||
this.readyState === 'complete') {
if (ps.indexOf(this) >= 0) {
this.onload = this.onreadystatechange = null;
//console.log("completed script: " + this.src);
ps.splice(ps.indexOf(this), 1);
// Call window.onscriptsload after last script loads
if (ps.length === 0 && window.onscriptsload) {
window.onscriptsload();
}
}
}
};
// In-order script execution tricks
if (Util.Engine.trident) {
// For IE wait until readyState is 'loaded' before
// appending it which will trigger execution
// http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
ls.push(script);
} else {
// For webkit and firefox set async=false and append now
// https://developer.mozilla.org/en-US/docs/HTML/Element/script
script.async = false;
head.appendChild(script);
}
ps.push(script);
}
}
// Get DOM element position on page
Util.getPosition = function (obj) {
var x = 0, y = 0;
if (obj.offsetParent) {
do {
x += obj.offsetLeft;
y += obj.offsetTop;
obj = obj.offsetParent;
} while (obj);
}
return {'x': x, 'y': y};
};
// Get mouse event position in DOM element
Util.getEventPosition = function (e, obj, scale) {
var evt, docX, docY, pos;
//if (!e) evt = window.event;
evt = (e ? e : window.event);
evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt);
if (evt.pageX || evt.pageY) {
docX = evt.pageX;
docY = evt.pageY;
} else if (evt.clientX || evt.clientY) {
docX = evt.clientX + document.body.scrollLeft +
document.documentElement.scrollLeft;
docY = evt.clientY + document.body.scrollTop +
document.documentElement.scrollTop;
}
pos = Util.getPosition(obj);
if (typeof scale === "undefined") {
scale = 1;
}
return {'x': (docX - pos.x) / scale, 'y': (docY - pos.y) / scale};
};
// Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
Util.addEvent = function (obj, evType, fn){
if (obj.attachEvent){
var r = obj.attachEvent("on"+evType, fn);
return r;
} else if (obj.addEventListener){
obj.addEventListener(evType, fn, false);
return true;
} else {
throw("Handler could not be attached");
}
};
Util.removeEvent = function(obj, evType, fn){
if (obj.detachEvent){
var r = obj.detachEvent("on"+evType, fn);
return r;
} else if (obj.removeEventListener){
obj.removeEventListener(evType, fn, false);
return true;
} else {
throw("Handler could not be removed");
}
};
Util.stopEvent = function(e) {
if (e.stopPropagation) { e.stopPropagation(); }
else { e.cancelBubble = true; }
if (e.preventDefault) { e.preventDefault(); }
else { e.returnValue = false; }
};
// Set browser engine versions. Based on mootools.
Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
Util.Engine = {
// Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
//'presto': (function() {
// return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
'presto': (function() { return (!window.opera) ? false : true; }()),
'trident': (function() {
return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
'webkit': (function() {
try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
//'webkit': (function() {
// return ((typeof navigator.taintEnabled !== "unknown") && navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); }()),
'gecko': (function() {
return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18); }())
};
if (Util.Engine.webkit) {
// Extract actual webkit version if available
Util.Engine.webkit = (function(v) {
var re = new RegExp('WebKit/([0-9\.]*) ');
v = (navigator.userAgent.match(re) || ['', v])[1];
return parseFloat(v, 10);
})(Util.Engine.webkit);
}
Util.Flash = (function(){
var v, version;
try {
v = navigator.plugins['Shockwave Flash'].description;
} catch(err1) {
try {
v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
} catch(err2) {
v = '0 r0';
}
}
version = v.match(/\d+/g);
return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
}());
@@ -0,0 +1,109 @@
* How to try
Assuming you have Web server (e.g. Apache) running at http://example.com/ .
- Download web_socket.rb from:
http://github.com/gimite/web-socket-ruby/tree/master
- Run sample Web Socket server (echo server) in example.com with: (#1)
$ ruby web-socket-ruby/samples/echo_server.rb example.com 10081
- If your server already provides socket policy file at port 843, modify the file to allow access to port 10081. Otherwise you can skip this step. See below for details.
- Publish the web-socket-js directory with your Web server (e.g. put it in ~/public_html).
- Change ws://localhost:10081 to ws://example.com:10081 in sample.html.
- Open sample.html in your browser.
- After "onopen" is shown, input something, click [Send] and confirm echo back.
#1: First argument of echo_server.rb means that it accepts Web Socket connection from HTML pages in example.com.
* Troubleshooting
If it doesn't work, try these:
1. Try Chrome and Firefox 3.x.
- It doesn't work on Chrome:
-- It's likely an issue of your code or the server. Debug your code as usual e.g. using console.log.
- It works on Chrome but it doesn't work on Firefox:
-- It's likely an issue of web-socket-js specific configuration (e.g. 3 and 4 below).
- It works on both Chrome and Firefox, but it doesn't work on your browser:
-- Check "Supported environment" section below. Your browser may not be supported by web-socket-js.
2. Add this line before your code:
WEB_SOCKET_DEBUG = true;
and use Developer Tools (Chrome/Safari) or Firebug (Firefox) to see if console.log outputs any errors.
3. Make sure you do NOT open your HTML page as local file e.g. file:///.../sample.html. web-socket-js doesn't work on local file. Open it via Web server e.g. http:///.../sample.html.
4. If you are NOT using web-socket-ruby as your WebSocket server, you need to place Flash socket policy file on your server. See "Flash socket policy file" section below for details.
5. Check if sample.html bundled with web-socket-js works.
6. Make sure the port used for WebSocket (10081 in example above) is not blocked by your server/client's firewall.
7. Install debugger version of Flash Player available here to see Flash errors:
http://www.adobe.com/support/flashplayer/downloads.html
* Supported environments
It should work on:
- Google Chrome 4 or later (just uses native implementation)
- Firefox 3.x, Internet Explorer 8 + Flash Player 9 or later
It may or may not work on other browsers such as Safari, Opera or IE 6. Patch for these browsers are appreciated, but I will not work on fixing issues specific to these browsers by myself.
* Flash socket policy file
This implementation uses Flash's socket, which means that your server must provide Flash socket policy file to declare the server accepts connections from Flash.
If you use web-socket-ruby available at
http://github.com/gimite/web-socket-ruby/tree/master
, you don't need anything special, because web-socket-ruby handles Flash socket policy file request. But if you already provide socket policy file at port 843, you need to modify the file to allow access to Web Socket port, because it precedes what web-socket-ruby provides.
If you use other Web Socket server implementation, you need to provide socket policy file yourself. See
http://www.lightsphere.com/dev/articles/flash_socket_policy.html
for details and sample script to run socket policy file server. node.js implementation is available here:
http://github.com/LearnBoost/Socket.IO-node/blob/master/lib/socket.io/transports/flashsocket.js
Actually, it's still better to provide socket policy file at port 843 even if you use web-socket-ruby. Flash always try to connect to port 843 first, so providing the file at port 843 makes startup faster.
* Cookie considerations
Cookie is sent if Web Socket host is the same as the origin of JavaScript. Otherwise it is not sent, because I don't know way to send right Cookie (which is Cookie of the host of Web Socket, I heard).
Note that it's technically possible that client sends arbitrary string as Cookie and any other headers (by modifying this library for example) once you place Flash socket policy file in your server. So don't trust Cookie and other headers if you allow connection from untrusted origin.
* Proxy considerations
The WebSocket spec (http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol) specifies instructions for User Agents to support proxied connections by implementing the HTTP CONNECT method.
The AS3 Socket class doesn't implement this mechanism, which renders it useless for the scenarios where the user trying to open a socket is behind a proxy.
The class RFC2817Socket (by Christian Cantrell) effectively lets us implement this, as long as the proxy settings are known and provided by the interface that instantiates the WebSocket. As such, if you want to support proxied conncetions, you'll have to supply this information to the WebSocket constructor when Flash is being used. One way to go about it would be to ask the user for proxy settings information if the initial connection fails.
* How to host HTML file and SWF file in different domains
By default, HTML file and SWF file must be in the same domain. You can follow steps below to allow hosting them in different domain.
WARNING: If you use the method below, HTML files in ANY domains can send arbitrary TCP data to your WebSocket server, regardless of configuration in Flash socket policy file. Arbitrary TCP data means that they can even fake request headers including Origin and Cookie.
- Unzip WebSocketMainInsecure.zip to extract WebSocketMainInsecure.swf.
- Put WebSocketMainInsecure.swf on your server, instead of WebSocketMain.swf.
- In JavaScript, set WEB_SOCKET_SWF_LOCATION to URL of your WebSocketMainInsecure.swf.
* How to build WebSocketMain.swf
Install Flex 4 SDK:
http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4
$ cd flash-src
$ ./build.sh
* License
New BSD License.
File diff suppressed because one or more lines are too long
+391
View File
@@ -0,0 +1,391 @@
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// License: New BSD License
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/rfc6455
(function() {
if (window.WEB_SOCKET_FORCE_FLASH) {
// Keeps going.
} else if (window.WebSocket) {
return;
} else if (window.MozWebSocket) {
// Firefox.
window.WebSocket = MozWebSocket;
return;
}
var logger;
if (window.WEB_SOCKET_LOGGER) {
logger = WEB_SOCKET_LOGGER;
} else if (window.console && window.console.log && window.console.error) {
// In some environment, console is defined but console.log or console.error is missing.
logger = window.console;
} else {
logger = {log: function(){ }, error: function(){ }};
}
// swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
if (swfobject.getFlashPlayerVersion().major < 10) {
logger.error("Flash Player >= 10.0.0 is required.");
return;
}
if (location.protocol == "file:") {
logger.error(
"WARNING: web-socket-js doesn't work in file:///... URL " +
"unless you set Flash Security Settings properly. " +
"Open the page via Web server i.e. http://...");
}
/**
* Our own implementation of WebSocket class using Flash.
* @param {string} url
* @param {array or string} protocols
* @param {string} proxyHost
* @param {int} proxyPort
* @param {string} headers
*/
window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
var self = this;
self.__id = WebSocket.__nextId++;
WebSocket.__instances[self.__id] = self;
self.readyState = WebSocket.CONNECTING;
self.bufferedAmount = 0;
self.__events = {};
if (!protocols) {
protocols = [];
} else if (typeof protocols == "string") {
protocols = [protocols];
}
// Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
// Otherwise, when onopen fires immediately, onopen is called before it is set.
self.__createTask = setTimeout(function() {
WebSocket.__addTask(function() {
self.__createTask = null;
WebSocket.__flash.create(
self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
});
}, 0);
};
/**
* Send data to the web socket.
* @param {string} data The data to send to the socket.
* @return {boolean} True for success, false for failure.
*/
WebSocket.prototype.send = function(data) {
if (this.readyState == WebSocket.CONNECTING) {
throw "INVALID_STATE_ERR: Web Socket connection has not been established";
}
// We use encodeURIComponent() here, because FABridge doesn't work if
// the argument includes some characters. We don't use escape() here
// because of this:
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
// But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
// preserve all Unicode characters either e.g. "\uffff" in Firefox.
// Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require
// additional testing.
var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
if (result < 0) { // success
return true;
} else {
this.bufferedAmount += result;
return false;
}
};
/**
* Close this web socket gracefully.
*/
WebSocket.prototype.close = function() {
if (this.__createTask) {
clearTimeout(this.__createTask);
this.__createTask = null;
this.readyState = WebSocket.CLOSED;
return;
}
if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
return;
}
this.readyState = WebSocket.CLOSING;
WebSocket.__flash.close(this.__id);
};
/**
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
*
* @param {string} type
* @param {function} listener
* @param {boolean} useCapture
* @return void
*/
WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
if (!(type in this.__events)) {
this.__events[type] = [];
}
this.__events[type].push(listener);
};
/**
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
*
* @param {string} type
* @param {function} listener
* @param {boolean} useCapture
* @return void
*/
WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
if (!(type in this.__events)) return;
var events = this.__events[type];
for (var i = events.length - 1; i >= 0; --i) {
if (events[i] === listener) {
events.splice(i, 1);
break;
}
}
};
/**
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
*
* @param {Event} event
* @return void
*/
WebSocket.prototype.dispatchEvent = function(event) {
var events = this.__events[event.type] || [];
for (var i = 0; i < events.length; ++i) {
events[i](event);
}
var handler = this["on" + event.type];
if (handler) handler.apply(this, [event]);
};
/**
* Handles an event from Flash.
* @param {Object} flashEvent
*/
WebSocket.prototype.__handleEvent = function(flashEvent) {
if ("readyState" in flashEvent) {
this.readyState = flashEvent.readyState;
}
if ("protocol" in flashEvent) {
this.protocol = flashEvent.protocol;
}
var jsEvent;
if (flashEvent.type == "open" || flashEvent.type == "error") {
jsEvent = this.__createSimpleEvent(flashEvent.type);
} else if (flashEvent.type == "close") {
jsEvent = this.__createSimpleEvent("close");
jsEvent.wasClean = flashEvent.wasClean ? true : false;
jsEvent.code = flashEvent.code;
jsEvent.reason = flashEvent.reason;
} else if (flashEvent.type == "message") {
var data = decodeURIComponent(flashEvent.message);
jsEvent = this.__createMessageEvent("message", data);
} else {
throw "unknown event type: " + flashEvent.type;
}
this.dispatchEvent(jsEvent);
};
WebSocket.prototype.__createSimpleEvent = function(type) {
if (document.createEvent && window.Event) {
var event = document.createEvent("Event");
event.initEvent(type, false, false);
return event;
} else {
return {type: type, bubbles: false, cancelable: false};
}
};
WebSocket.prototype.__createMessageEvent = function(type, data) {
if (document.createEvent && window.MessageEvent && !window.opera) {
var event = document.createEvent("MessageEvent");
event.initMessageEvent("message", false, false, data, null, null, window, null);
return event;
} else {
// IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
return {type: type, data: data, bubbles: false, cancelable: false};
}
};
/**
* Define the WebSocket readyState enumeration.
*/
WebSocket.CONNECTING = 0;
WebSocket.OPEN = 1;
WebSocket.CLOSING = 2;
WebSocket.CLOSED = 3;
// Field to check implementation of WebSocket.
WebSocket.__isFlashImplementation = true;
WebSocket.__initialized = false;
WebSocket.__flash = null;
WebSocket.__instances = {};
WebSocket.__tasks = [];
WebSocket.__nextId = 0;
/**
* Load a new flash security policy file.
* @param {string} url
*/
WebSocket.loadFlashPolicyFile = function(url){
WebSocket.__addTask(function() {
WebSocket.__flash.loadManualPolicyFile(url);
});
};
/**
* Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
*/
WebSocket.__initialize = function() {
if (WebSocket.__initialized) return;
WebSocket.__initialized = true;
if (WebSocket.__swfLocation) {
// For backword compatibility.
window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
}
if (!window.WEB_SOCKET_SWF_LOCATION) {
logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
return;
}
if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
!WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
var swfHost = RegExp.$1;
if (location.host != swfHost) {
logger.error(
"[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
"('" + location.host + "' != '" + swfHost + "'). " +
"See also 'How to host HTML file and SWF file in different domains' section " +
"in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
"by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
}
}
var container = document.createElement("div");
container.id = "webSocketContainer";
// Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
// Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
// But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
// Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
// the best we can do as far as we know now.
container.style.position = "absolute";
if (WebSocket.__isFlashLite()) {
container.style.left = "0px";
container.style.top = "0px";
} else {
container.style.left = "-100px";
container.style.top = "-100px";
}
var holder = document.createElement("div");
holder.id = "webSocketFlash";
container.appendChild(holder);
document.body.appendChild(container);
// See this article for hasPriority:
// http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
swfobject.embedSWF(
WEB_SOCKET_SWF_LOCATION,
"webSocketFlash",
"1" /* width */,
"1" /* height */,
"10.0.0" /* SWF version */,
null,
null,
{hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
null,
function(e) {
if (!e.success) {
logger.error("[WebSocket] swfobject.embedSWF failed");
}
}
);
};
/**
* Called by Flash to notify JS that it's fully loaded and ready
* for communication.
*/
WebSocket.__onFlashInitialized = function() {
// We need to set a timeout here to avoid round-trip calls
// to flash during the initialization process.
setTimeout(function() {
WebSocket.__flash = document.getElementById("webSocketFlash");
WebSocket.__flash.setCallerUrl(location.href);
WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
for (var i = 0; i < WebSocket.__tasks.length; ++i) {
WebSocket.__tasks[i]();
}
WebSocket.__tasks = [];
}, 0);
};
/**
* Called by Flash to notify WebSockets events are fired.
*/
WebSocket.__onFlashEvent = function() {
setTimeout(function() {
try {
// Gets events using receiveEvents() instead of getting it from event object
// of Flash event. This is to make sure to keep message order.
// It seems sometimes Flash events don't arrive in the same order as they are sent.
var events = WebSocket.__flash.receiveEvents();
for (var i = 0; i < events.length; ++i) {
WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
}
} catch (e) {
logger.error(e);
}
}, 0);
return true;
};
// Called by Flash.
WebSocket.__log = function(message) {
logger.log(decodeURIComponent(message));
};
// Called by Flash.
WebSocket.__error = function(message) {
logger.error(decodeURIComponent(message));
};
WebSocket.__addTask = function(task) {
if (WebSocket.__flash) {
task();
} else {
WebSocket.__tasks.push(task);
}
};
/**
* Test if the browser is running flash lite.
* @return {boolean} True if flash lite is running, false otherwise.
*/
WebSocket.__isFlashLite = function() {
if (!window.navigator || !window.navigator.mimeTypes) {
return false;
}
var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
return false;
}
return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
};
if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
// NOTE:
// This fires immediately if web_socket.js is dynamically loaded after
// the document is loaded.
swfobject.addDomLoadEvent(function() {
WebSocket.__initialize();
});
}
})();
+424
View File
@@ -0,0 +1,424 @@
/*
* Websock: high-performance binary WebSockets
* Copyright (C) 2012 Joel Martin
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* Websock is similar to the standard WebSocket object but Websock
* enables communication with raw TCP sockets (i.e. the binary stream)
* via websockify. This is accomplished by base64 encoding the data
* stream between Websock and websockify.
*
* Websock has built-in receive queue buffering; the message event
* does not contain actual data but is simply a notification that
* there is new data available. Several rQ* methods are available to
* read binary data off of the receive queue.
*/
/*jslint browser: true, bitwise: false, plusplus: false */
/*global Util, Base64 */
// Load Flash WebSocket emulator if needed
// To force WebSocket emulator even when native WebSocket available
//window.WEB_SOCKET_FORCE_FLASH = true;
// To enable WebSocket emulator debug:
//window.WEB_SOCKET_DEBUG=1;
if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = true;
} else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = true;
window.WebSocket = window.MozWebSocket;
} else {
/* no builtin WebSocket so load web_socket.js */
Websock_native = false;
(function () {
window.WEB_SOCKET_SWF_LOCATION = Util.get_include_uri() +
"web-socket-js/WebSocketMain.swf";
if (Util.Engine.trident) {
Util.Debug("Forcing uncached load of WebSocketMain.swf");
window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
}
Util.load_scripts(["web-socket-js/swfobject.js",
"web-socket-js/web_socket.js"]);
}());
}
function Websock() {
"use strict";
var api = {}, // Public API
websocket = null, // WebSocket object
mode = 'base64', // Current WebSocket mode: 'binary', 'base64'
rQ = [], // Receive queue
rQi = 0, // Receive queue index
rQmax = 10000, // Max receive queue size before compacting
sQ = [], // Send queue
eventHandlers = {
'message' : function() {},
'open' : function() {},
'close' : function() {},
'error' : function() {}
},
test_mode = false;
//
// Queue public functions
//
function get_sQ() {
return sQ;
}
function get_rQ() {
return rQ;
}
function get_rQi() {
return rQi;
}
function set_rQi(val) {
rQi = val;
}
function rQlen() {
return rQ.length - rQi;
}
function rQpeek8() {
return (rQ[rQi] );
}
function rQshift8() {
return (rQ[rQi++] );
}
function rQunshift8(num) {
if (rQi === 0) {
rQ.unshift(num);
} else {
rQi -= 1;
rQ[rQi] = num;
}
}
function rQshift16() {
return (rQ[rQi++] << 8) +
(rQ[rQi++] );
}
function rQshift32() {
return (rQ[rQi++] << 24) +
(rQ[rQi++] << 16) +
(rQ[rQi++] << 8) +
(rQ[rQi++] );
}
function rQshiftStr(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
var arr = rQ.slice(rQi, rQi + len);
rQi += len;
return String.fromCharCode.apply(null, arr);
}
function rQshiftBytes(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
rQi += len;
return rQ.slice(rQi-len, rQi);
}
function rQslice(start, end) {
if (end) {
return rQ.slice(rQi + start, rQi + end);
} else {
return rQ.slice(rQi + start);
}
}
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
// to be available in the receive queue. Return true if we need to
// wait (and possibly print a debug message), otherwise false.
function rQwait(msg, num, goback) {
var rQlen = rQ.length - rQi; // Skip rQlen() function call
if (rQlen < num) {
if (goback) {
if (rQi < goback) {
throw("rQwait cannot backup " + goback + " bytes");
}
rQi -= goback;
}
//Util.Debug(" waiting for " + (num-rQlen) +
// " " + msg + " byte(s)");
return true; // true means need more data
}
return false;
}
//
// Private utility routines
//
function encode_message() {
if (mode === 'binary') {
// Put in a binary arraybuffer
return (new Uint8Array(sQ)).buffer;
} else {
// base64 encode
return Base64.encode(sQ);
}
}
function decode_message(data) {
//Util.Debug(">> decode_message: " + data);
if (mode === 'binary') {
// push arraybuffer values onto the end
var u8 = new Uint8Array(data);
for (var i = 0; i < u8.length; i++) {
rQ.push(u8[i]);
}
} else {
// base64 decode and concat to the end
rQ = rQ.concat(Base64.decode(data, 0));
}
//Util.Debug(">> decode_message, rQ: " + rQ);
}
//
// Public Send functions
//
function flush() {
if (websocket.bufferedAmount !== 0) {
Util.Debug("bufferedAmount: " + websocket.bufferedAmount);
}
if (websocket.bufferedAmount < api.maxBufferedAmount) {
//Util.Debug("arr: " + arr);
//Util.Debug("sQ: " + sQ);
if (sQ.length > 0) {
websocket.send(encode_message(sQ));
sQ = [];
}
return true;
} else {
Util.Info("Delaying send, bufferedAmount: " +
websocket.bufferedAmount);
return false;
}
}
// overridable for testing
function send(arr) {
//Util.Debug(">> send_array: " + arr);
sQ = sQ.concat(arr);
return flush();
}
function send_string(str) {
//Util.Debug(">> send_string: " + str);
api.send(str.split('').map(
function (chr) { return chr.charCodeAt(0); } ) );
}
//
// Other public functions
function recv_message(e) {
//Util.Debug(">> recv_message: " + e.data.length);
try {
decode_message(e.data);
if (rQlen() > 0) {
eventHandlers.message();
// Compact the receive queue
if (rQ.length > rQmax) {
//Util.Debug("Compacting receive queue");
rQ = rQ.slice(rQi);
rQi = 0;
}
} else {
Util.Debug("Ignoring empty message");
}
} catch (exc) {
if (typeof exc.stack !== 'undefined') {
Util.Warn("recv_message, caught exception: " + exc.stack);
} else if (typeof exc.description !== 'undefined') {
Util.Warn("recv_message, caught exception: " + exc.description);
} else {
Util.Warn("recv_message, caught exception:" + exc);
}
if (typeof exc.name !== 'undefined') {
eventHandlers.error(exc.name + ": " + exc.message);
} else {
eventHandlers.error(exc);
}
}
//Util.Debug("<< recv_message");
}
// Set event handlers
function on(evt, handler) {
eventHandlers[evt] = handler;
}
function init(protocols, ws_schema) {
rQ = [];
rQi = 0;
sQ = [];
websocket = null;
var bt = false,
wsbt = false,
try_binary = false;
// Check for full typed array support
if (('Uint8Array' in window) &&
('set' in Uint8Array.prototype)) {
bt = true;
}
// Check for full binary type support in WebSocket
// Inspired by:
// https://github.com/Modernizr/Modernizr/issues/370
// https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets/binary.js
try {
if (bt && ('binaryType' in WebSocket.prototype ||
!!(new WebSocket(ws_schema + '://.').binaryType))) {
Util.Info("Detected binaryType support in WebSockets");
wsbt = true;
}
} catch (exc) {
// Just ignore failed test localhost connections
}
// Default protocols if not specified
if (typeof(protocols) === "undefined") {
if (wsbt) {
protocols = ['binary', 'base64'];
} else {
protocols = 'base64';
}
}
// If no binary support, make sure it was not requested
if (!wsbt) {
if (protocols === 'binary') {
throw("WebSocket binary sub-protocol requested but not supported");
}
if (typeof(protocols) === "object") {
var new_protocols = [];
for (var i = 0; i < protocols.length; i++) {
if (protocols[i] === 'binary') {
Util.Error("Skipping unsupported WebSocket binary sub-protocol");
} else {
new_protocols.push(protocols[i]);
}
}
if (new_protocols.length > 0) {
protocols = new_protocols;
} else {
throw("Only WebSocket binary sub-protocol was requested and not supported.");
}
}
}
return protocols;
}
function open(uri, protocols) {
var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
protocols = init(protocols, ws_schema);
if (test_mode) {
websocket = {};
} else {
websocket = new WebSocket(uri, protocols);
if (protocols.indexOf('binary') >= 0) {
websocket.binaryType = 'arraybuffer';
}
}
websocket.onmessage = recv_message;
websocket.onopen = function() {
Util.Debug(">> WebSock.onopen");
if (websocket.protocol) {
mode = websocket.protocol;
Util.Info("Server chose sub-protocol: " + websocket.protocol);
} else {
mode = 'base64';
Util.Error("Server select no sub-protocol!: " + websocket.protocol);
}
eventHandlers.open();
Util.Debug("<< WebSock.onopen");
};
websocket.onclose = function(e) {
Util.Debug(">> WebSock.onclose");
eventHandlers.close(e);
Util.Debug("<< WebSock.onclose");
};
websocket.onerror = function(e) {
Util.Debug(">> WebSock.onerror: " + e);
eventHandlers.error(e);
Util.Debug("<< WebSock.onerror");
};
}
function close() {
if (websocket) {
if ((websocket.readyState === WebSocket.OPEN) ||
(websocket.readyState === WebSocket.CONNECTING)) {
Util.Info("Closing WebSocket connection");
websocket.close();
}
websocket.onmessage = function (e) { return; };
}
}
// Override internal functions for testing
// Takes a send function, returns reference to recv function
function testMode(override_send, data_mode) {
test_mode = true;
mode = data_mode;
api.send = override_send;
api.close = function () {};
return recv_message;
}
function constructor() {
// Configuration settings
api.maxBufferedAmount = 200;
// Direct access to send and receive queues
api.get_sQ = get_sQ;
api.get_rQ = get_rQ;
api.get_rQi = get_rQi;
api.set_rQi = set_rQi;
// Routines to read from the receive queue
api.rQlen = rQlen;
api.rQpeek8 = rQpeek8;
api.rQshift8 = rQshift8;
api.rQunshift8 = rQunshift8;
api.rQshift16 = rQshift16;
api.rQshift32 = rQshift32;
api.rQshiftStr = rQshiftStr;
api.rQshiftBytes = rQshiftBytes;
api.rQslice = rQslice;
api.rQwait = rQwait;
api.flush = flush;
api.send = send;
api.send_string = send_string;
api.on = on;
api.init = init;
api.open = open;
api.close = close;
api.testMode = testMode;
return api;
}
return constructor();
}
+216
View File
@@ -0,0 +1,216 @@
/*
* from noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
"use strict";
/*jslint bitwise: false, white: false */
/*global Util, window, document */
// Globals defined here
var WebUtil = {}, $D;
/*
* Simple DOM selector by ID
*/
if (!window.$D) {
window.$D = function (id) {
if (document.getElementById) {
return document.getElementById(id);
} else if (document.all) {
return document.all[id];
} else if (document.layers) {
return document.layers[id];
}
return undefined;
};
}
/*
* ------------------------------------------------------
* Namespaced in WebUtil
* ------------------------------------------------------
*/
// init log level reading the logging HTTP param
WebUtil.init_logging = function(level) {
if (typeof level !== "undefined") {
Util._log_level = level;
} else {
Util._log_level = (document.location.href.match(
/logging=([A-Za-z0-9\._\-]*)/) ||
['', Util._log_level])[1];
}
Util.init_logging();
};
WebUtil.dirObj = function (obj, depth, parent) {
var i, msg = "", val = "";
if (! depth) { depth=2; }
if (! parent) { parent= ""; }
// Print the properties of the passed-in object
for (i in obj) {
if ((depth > 1) && (typeof obj[i] === "object")) {
// Recurse attributes that are objects
msg += WebUtil.dirObj(obj[i], depth-1, parent + "." + i);
} else {
//val = new String(obj[i]).replace("\n", " ");
if (typeof(obj[i]) === "undefined") {
val = "undefined";
} else {
val = obj[i].toString().replace("\n", " ");
}
if (val.length > 30) {
val = val.substr(0,30) + "...";
}
msg += parent + "." + i + ": " + val + "\n";
}
}
return msg;
};
// Read a query string variable
WebUtil.getQueryVar = function(name, defVal) {
var re = new RegExp('[?][^#]*' + name + '=([^&#]*)'),
match = document.location.href.match(re);
if (typeof defVal === 'undefined') { defVal = null; }
if (match) {
return decodeURIComponent(match[1]);
} else {
return defVal;
}
};
/*
* Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html
*/
// No days means only for this browser session
WebUtil.createCookie = function(name,value,days) {
var date, expires;
if (days) {
date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
expires = "; expires="+date.toGMTString();
}
else {
expires = "";
}
document.cookie = name+"="+value+expires+"; path=/";
};
WebUtil.readCookie = function(name, defaultValue) {
var i, c, nameEQ = name + "=", ca = document.cookie.split(';');
for(i=0; i < ca.length; i += 1) {
c = ca[i];
while (c.charAt(0) === ' ') { c = c.substring(1,c.length); }
if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); }
}
return (typeof defaultValue !== 'undefined') ? defaultValue : null;
};
WebUtil.eraseCookie = function(name) {
WebUtil.createCookie(name,"",-1);
};
/*
* Setting handling.
*/
WebUtil.initSettings = function(callback) {
var callbackArgs = Array.prototype.slice.call(arguments, 1);
if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.get(function (cfg) {
WebUtil.settings = cfg;
console.log(WebUtil.settings);
if (callback) {
callback.apply(this, callbackArgs);
}
});
} else {
// No-op
if (callback) {
callback.apply(this, callbackArgs);
}
}
};
// No days means only for this browser session
WebUtil.writeSetting = function(name, value) {
if (window.chrome && window.chrome.storage) {
//console.log("writeSetting:", name, value);
if (WebUtil.settings[name] !== value) {
WebUtil.settings[name] = value;
window.chrome.storage.sync.set(WebUtil.settings);
}
} else {
localStorage.setItem(name, value);
}
};
WebUtil.readSetting = function(name, defaultValue) {
var value;
if (window.chrome && window.chrome.storage) {
value = WebUtil.settings[name];
} else {
value = localStorage.getItem(name);
}
if (typeof value === "undefined") {
value = null;
}
if (value === null && typeof defaultValue !== undefined) {
return defaultValue;
} else {
return value;
}
};
WebUtil.eraseSetting = function(name) {
if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.remove(name);
delete WebUtil.settings[name];
} else {
localStorage.removeItem(name);
}
};
/*
* Alternate stylesheet selection
*/
WebUtil.getStylesheets = function() { var i, links, sheets = [];
links = document.getElementsByTagName("link");
for (i = 0; i < links.length; i += 1) {
if (links[i].title &&
links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) {
sheets.push(links[i]);
}
}
return sheets;
};
// No sheet means try and use value from cookie, null sheet used to
// clear all alternates.
WebUtil.selectStylesheet = function(sheet) {
var i, link, sheets = WebUtil.getStylesheets();
if (typeof sheet === 'undefined') {
sheet = 'default';
}
for (i=0; i < sheets.length; i += 1) {
link = sheets[i];
if (link.title === sheet) {
Util.Debug("Using stylesheet " + sheet);
link.disabled = false;
} else {
//Util.Debug("Skipping stylesheet " + link.title);
link.disabled = true;
}
}
return sheet;
};
+235
View File
@@ -0,0 +1,235 @@
/*
* WebSockets IRC 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
* IRC Client protocol:
* http://www.faqs.org/rfcs/rfc2812.html
*/
function IRC(target, connect_callback, disconnect_callback) {
var that = {}, // Public API interface
vt100, ws, sQ = [],
state = "unconnected",
irc_nick, irc_channel,
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 rQ, rQi, i;
while (ws.rQlen() > 1) {
rQ = ws.get_rQ();
rQi = ws.get_rQi();
for (i = rQi; i < rQ.length; i++) {
if (rQ[i] === 10) {
break;
}
}
if (i >= rQ.length) {
// No line break found
break;
}
recvMsg(ws.rQshiftStr((i-rQi) + 1));
}
//console.log("<< do_recv");
}
// Handle an IRC message
function recvMsg(msg) {
Util.Debug(">> recvMsg('" + msg + "')");
var tokens = msg.split(' '), in_params = true,
prefix, command, params = [], trailing = [];
Util.Info(" tokens: " + tokens);
if (tokens[0].charAt(0) === ":") {
prefix = tokens.shift();
}
command = tokens.shift();
while (tokens.length > 0) {
if (tokens[0].charAt(0) === ":") {
in_params = false;
}
if (in_params) {
params.push(tokens.shift());
} else {
trailing.push(tokens.shift());
}
}
Util.Info(" prefix: " + prefix);
Util.Info(" command: " + command);
Util.Info(" params: " + params);
Util.Info(" trailing: " + trailing);
// Show raw received
vt100.write(msg);
switch (command) {
case "004":
state = "registered";
vt100.write("Joining channel #" + irc_channel);
sendCmd("JOIN #" + irc_channel);
break;
case "JOIN":
state = "joined";
vt100.write("Joined channel #" + irc_channel);
break;
}
Util.Debug("<< recvMsg('" + msg + "')");
}
function sendCmd(msg) {
Util.Info("Sending: " + msg);
sQ.pushStr(msg + "\r\n");
do_send();
}
that.sendMsg = function(msg) {
// TODO parse into message
sendCmd("PRIVMSG #" + irc_channel + " :" + msg);
}
that.connect = function(host, port, encrypt, nick, channel) {
var host = host,
port = port,
scheme = "ws://", uri;
irc_nick = nick;
irc_channel = channel;
Util.Debug(">> connect");
if ((!host) || (!port)) {
alert("must set host and port");
return false;
}
if (ws) {
ws.close();
}
if (encrypt) {
scheme = "wss://";
}
uri = scheme + host + ":" + port;
Util.Info("connecting to " + uri);
ws.open(uri);
Util.Debug("<< connect");
return true;
}
that.disconnect = function() {
Util.Debug(">> disconnect");
if (ws) {
ws.close();
}
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");
// Send registration commands
state = "connected";
sendCmd("NICK " + irc_nick);
// TODO: how to determine this?
sendCmd("USER joelm 0 * :Joel Martin");
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);
// Show cursor
vt100.curs_set(true, false);
/*
* 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);
}
return that;
}
return constructor(); // Return the public API interface
} // End of Telnet()
+335
View File
@@ -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()