diff options
author | Alon Zakai <alonzakai@gmail.com> | 2012-10-30 10:52:24 -0700 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2012-10-30 10:52:24 -0700 |
commit | fcb6c33bac763d7a8246674eff33b599c28c1b6e (patch) | |
tree | 15fc927f186a0de2476729a1c1e1906478f82e7e /third_party | |
parent | 5ba8486b1cd87d31f3d911cbfeb126717e0865b1 (diff) |
update websockify to latest trunk
Diffstat (limited to 'third_party')
-rw-r--r-- | third_party/websockify/CHANGES.txt | 5 | ||||
-rw-r--r-- | third_party/websockify/LICENSE.txt | 11 | ||||
-rw-r--r-- | third_party/websockify/README.md | 21 | ||||
-rw-r--r-- | third_party/websockify/include/base64.js | 99 | ||||
-rw-r--r-- | third_party/websockify/include/util.js | 62 | ||||
-rw-r--r-- | third_party/websockify/include/websock.js | 25 | ||||
-rw-r--r-- | third_party/websockify/include/webutil.js | 88 | ||||
-rwxr-xr-x[-rw-r--r--] | third_party/websockify/other/websockify.js | 93 | ||||
-rw-r--r-- | third_party/websockify/setup.py | 2 | ||||
-rw-r--r-- | third_party/websockify/websockify/websocket.py | 56 | ||||
-rwxr-xr-x | third_party/websockify/websockify/websocketproxy.py | 45 |
11 files changed, 343 insertions, 164 deletions
diff --git a/third_party/websockify/CHANGES.txt b/third_party/websockify/CHANGES.txt index cbd3f73f..0ed52b80 100644 --- a/third_party/websockify/CHANGES.txt +++ b/third_party/websockify/CHANGES.txt @@ -1,6 +1,11 @@ Changes ======= +0.2.1 - Oct 15, 2012 +-------------------- + + * re-released with updated version number + 0.2.0 - Sep 17, 2012 -------------------- diff --git a/third_party/websockify/LICENSE.txt b/third_party/websockify/LICENSE.txt index 916a77a0..f1648dec 100644 --- a/third_party/websockify/LICENSE.txt +++ b/third_party/websockify/LICENSE.txt @@ -1,11 +1,16 @@ websockify is licensed under the LGPL version 3 (see docs/LICENSE.GPL-3 and docs/LICENSE.LGPL-3) with the following exceptions: - include/base64.js : Choice of MIT 1.1, GPL-2 or LGPL-2.1 - - include/web-socket-js/ : New BSD license. Source code at + include/websock.js : MPL 2.0 + + include/base64.js : MPL 2.0 + + include/des.js : Various BSD style licenses + + include/web-socket-js/ : New BSD license (3-clause). Source code at https://github.com/gimite/web-socket-js other/kumina.c : Simplified BSD license (2 clause). Original source at https://github.com/kumina/wsproxy + diff --git a/third_party/websockify/README.md b/third_party/websockify/README.md index 48c790af..2cfac0f5 100644 --- a/third_party/websockify/README.md +++ b/third_party/websockify/README.md @@ -11,11 +11,24 @@ the target in both directions. ### WebSockets binary data Websockify supports all versions of the WebSockets protocol (Hixie and -HyBI). The older Hixie versions of the protocol only support UTF-8 +HyBi). The older Hixie versions of the protocol only support UTF-8 text payloads. In order to transport binary data over UTF-8 an -encoding must used to encapsulate the data within UTF-8. Websockify -uses base64 to encode all traffic to and from the client. This does -not affect the data between websockify and the server. +encoding must used to encapsulate the data within UTF-8. + +With Hixie clients, Websockify uses base64 to encode all traffic to +and from the client. This does not affect the data between websockify +and the server. + +With HyBi clients, websockify negotiates whether to base64 encode +traffic to and from the client via the subprotocol header +(Sec-WebSocket-Protocol). The valid subprotocol values are 'binary' +and 'base64' and if the client sends both then the server (the python +implementation) will prefer 'binary'. The 'binary' subprotocol +indicates that the data will be sent raw using binary WebSocket +frames. Some HyBi clients (such as the Flash fallback and older Chrome +and iOS versions) do not support binary data which is why the +negotiation is necessary. + ### Encrypted WebSocket connections (wss://) diff --git a/third_party/websockify/include/base64.js b/third_party/websockify/include/base64.js index e9b3c522..87336127 100644 --- a/third_party/websockify/include/base64.js +++ b/third_party/websockify/include/base64.js @@ -1,45 +1,8 @@ -/* - * Modified from: - * http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956 - */ - -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla XML-RPC Client component. - * - * The Initial Developer of the Original Code is - * Digital Creations 2, Inc. - * Portions created by the Initial Developer are Copyright (C) 2000 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Martijn Pieters <mj@digicool.com> (original author) - * Samuel Sieb <samuel@sieb.net> - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ +/* 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 */ @@ -47,35 +10,37 @@ var Base64 = { /* Convert data (an array of integers) to a Base64 string. */ -toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', +toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''), base64Pad : '=', encode: function (data) { "use strict"; - var result = '', - chrTable = Base64.toBase64Table.split(''), - pad = Base64.base64Pad, - length = data.length, - i; + 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 += chrTable[data[i] >> 2]; - result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; - result += chrTable[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)]; - result += chrTable[data[i+2] & 0x3f]; + 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 += chrTable[data[i] >> 2]; + result += toBase64Table[data[i] >> 2]; if ((length%3) === 2) { - result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; - result += chrTable[(data[i+1] & 0x0f) << 2]; - result += pad; + result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; + result += toBase64Table[(data[i+1] & 0x0f) << 2]; + result += base64Pad; } else { - result += chrTable[(data[i] & 0x03) << 4]; - result += pad + pad; + result += toBase64Table[(data[i] & 0x03) << 4]; + result += base64Pad + base64Pad; } } @@ -97,12 +62,12 @@ toBinaryTable : [ decode: function (data, offset) { "use strict"; offset = typeof(offset) !== 'undefined' ? offset : 0; - var binTable = Base64.toBinaryTable, - pad = Base64.base64Pad, - result, result_length, idx, i, c, padding, - leftbits = 0, // number of bits decoded, but yet to be appended - leftdata = 0, // bits decoded, but yet to be appended - data_length = data.indexOf('=') - offset; + 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; } @@ -111,9 +76,10 @@ decode: function (data, offset) { result = new Array(result_length); // Convert one by one. + /* BEGIN LOOP */ for (idx = 0, i = offset; i < data.length; i++) { - c = binTable[data.charCodeAt(i) & 0x7f]; - padding = (data.charAt(i) === pad); + 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); @@ -134,6 +100,7 @@ decode: function (data, offset) { leftdata &= (1 << leftbits) - 1; } } + /* END LOOP */ // If there are any bits left, the base64 string was corrupted if (leftbits) { diff --git a/third_party/websockify/include/util.js b/third_party/websockify/include/util.js index ddc1914c..ab2e1c1e 100644 --- a/third_party/websockify/include/util.js +++ b/third_party/websockify/include/util.js @@ -1,7 +1,7 @@ /* - * noVNC: HTML5 VNC client - * Copyright (C) 2011 Joel Martin - * Licensed under LGPL-3 (see LICENSE.txt) + * 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. */ @@ -57,6 +57,21 @@ if (!Array.prototype.map) }; } +// +// 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 @@ -131,6 +146,8 @@ Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) { } } 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 () {}; @@ -190,6 +207,45 @@ Util.conf_defaults = function(cfg, api, defaults, arr) { * 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._pending_scripts = []; +Util.load_scripts = function(files) { + var head = document.getElementsByTagName('head')[0], + ps = Util._pending_scripts; + for (var f=0; f<files.length; f++) { + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = Util.get_include_uri() + files[f]; + //console.log("loading script: " + Util.get_include_uri() + files[f]); + head.appendChild(script); + ps.push(script); + script.onload = script.onreadystatechange = function (e) { + if (!this.readyState || + this.readyState == 'complete' || + this.readyState == 'loaded') { + this.onload = this.onreadystatechange = null; + if (ps.indexOf(this) >= 0) { + //console.log("loaded script: " + this.src); + ps.splice(ps.indexOf(this), 1); + } + // Call window.onscriptsload after last script loads + if (ps.length === 0 && window.onscriptsload) { + window.onscriptsload(); + } + } + } + } +} + // Get DOM element position on page Util.getPosition = function (obj) { var x = 0, y = 0; diff --git a/third_party/websockify/include/websock.js b/third_party/websockify/include/websock.js index ccb7d4c1..7d12644e 100644 --- a/third_party/websockify/include/websock.js +++ b/third_party/websockify/include/websock.js @@ -1,7 +1,7 @@ /* * Websock: high-performance binary WebSockets * Copyright (C) 2012 Joel Martin - * Licensed under LGPL-3 (see LICENSE.txt) + * 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) @@ -35,23 +35,14 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) { Websock_native = false; (function () { - function get_INCLUDE_URI() { - return (typeof INCLUDE_URI !== "undefined") ? - INCLUDE_URI : "include/"; - } - - var start = "<script src='" + get_INCLUDE_URI(), - end = "'><\/script>", extra = ""; - - window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() + + 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(); } - extra += start + "web-socket-js/swfobject.js" + end; - extra += start + "web-socket-js/web_socket.js" + end; - document.write(extra); + Util.load_scripts(["web-socket-js/swfobject.js", + "web-socket-js/web_socket.js"]); }()); } @@ -181,7 +172,10 @@ function decode_message(data) { //Util.Debug(">> decode_message: " + data); if (mode === 'binary') { // push arraybuffer values onto the end - rQ.push.apply(rQ, (new Uint8Array(data))); + 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)); @@ -380,8 +374,9 @@ function close() { // Override internal functions for testing // Takes a send function, returns reference to recv function -function testMode(override_send) { +function testMode(override_send, data_mode) { test_mode = true; + mode = data_mode; api.send = override_send; api.close = function () {}; return recv_message; diff --git a/third_party/websockify/include/webutil.js b/third_party/websockify/include/webutil.js index 2966cd79..2c82c73c 100644 --- a/third_party/websockify/include/webutil.js +++ b/third_party/websockify/include/webutil.js @@ -1,7 +1,7 @@ /* * from noVNC: HTML5 VNC client - * Copyright (C) 2010 Joel Martin - * Licensed under LGPL-3 (see LICENSE.txt) + * Copyright (C) 2012 Joel Martin + * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. */ @@ -37,14 +37,16 @@ if (!window.$D) { */ // init log level reading the logging HTTP param -WebUtil.init_logging = function() { - Util._log_level = (document.location.href.match( - /logging=([A-Za-z0-9\._\-]*)/) || - ['', Util._log_level])[1]; - +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.init_logging(); WebUtil.dirObj = function (obj, depth, parent) { @@ -75,9 +77,14 @@ WebUtil.dirObj = function (obj, depth, parent) { // Read a query string variable WebUtil.getQueryVar = function(name, defVal) { - var re = new RegExp('[?][^#]*' + name + '=([^&#]*)'); + var re = new RegExp('[?][^#]*' + name + '=([^&#]*)'), + match = document.location.href.match(re); if (typeof defVal === 'undefined') { defVal = null; } - return (document.location.href.match(re) || ['',defVal])[1]; + if (match) { + return decodeURIComponent(match[1]); + } else { + return defVal; + } }; @@ -114,6 +121,67 @@ WebUtil.eraseCookie = function(name) { }; /* + * 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 = []; diff --git a/third_party/websockify/other/websockify.js b/third_party/websockify/other/websockify.js index cda1aaf9..10447587 100644..100755 --- a/third_party/websockify/other/websockify.js +++ b/third_party/websockify/other/websockify.js @@ -1,14 +1,26 @@ +#!/usr/bin/env node + // A WebSocket to TCP socket proxy // Copyright 2012 Joel Martin // Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3) -// Known to work with node 0.6 +// Known to work with node 0.8.9 // Requires node modules: ws, base64, optimist and policyfile // npm install ws base64 optimist policyfile +// +// NOTE: +// This version requires a patched version of einaros/ws that supports +// subprotocol negotiation. You can use the patched version like this: +// +// cd websockify/other +// git clone https://github.com/kanaka/ws +// npm link ./ws + var argv = require('optimist').argv, net = require('net'), http = require('http'), + https = require('https'), url = require('url'), path = require('path'), fs = require('fs'), @@ -18,36 +30,55 @@ var argv = require('optimist').argv, Buffer = require('buffer').Buffer, WebSocketServer = require('ws').Server, - httpServer, wsServer, + webServer, wsServer, source_host, source_port, target_host, target_port, web_path = null; // Handle new WebSocket client new_client = function(client) { - console.log('WebSocket client connected'); - //console.log('protocol: ' + client.protocol); - - var target = net.createConnection(target_port,target_host); - target.on('begin', function() { - console.log('connected to target'); + var clientAddr = client._socket.remoteAddress, log; + log = function (msg) { + console.log(' ' + clientAddr + ': '+ msg); + }; + log('WebSocket connection'); + log('Version ' + client.protocolVersion + ', subprotocol: ' + client.protocol); + + var target = net.createConnection(target_port,target_host, function() { + log('connected to target'); }); target.on('data', function(data) { - client.send(base64.encode(new Buffer(data))); + //log("sending message: " + data); + try { + if (client.protocol === 'base64') { + client.send(base64.encode(new Buffer(data))); + } else { + client.send(data,{binary: true}); + } + } catch(e) { + log("Client closed, cleaning up target"); + target.end(); + } }); target.on('end', function() { - console.log('target disconnected'); + log('target disconnected'); }); client.on('message', function(msg) { - //console.log('got some message'); - target.write(base64.decode(msg),'binary'); + //log('got message: ' + msg); + if (client.protocol === 'base64') { + target.write(base64.decode(msg),'binary'); + } else { + target.write(msg,'binary'); + } }); client.on('close', function(code, reason) { - console.log('WebSocket client disconnected: ' + code + ' [' + reason + ']'); + log('WebSocket client disconnected: ' + code + ' [' + reason + ']'); + target.end(); }); client.on('error', function(a) { - console.log('WebSocket client error: ' + a); + log('WebSocket client error: ' + a); + target.end(); }); }; @@ -94,6 +125,20 @@ http_request = function (request, response) { }); }; +// Select 'binary' or 'base64' subprotocol, preferring 'binary' +selectProtocol = function(protocols, callback) { + var plist = protocols ? protocols.split(',') : ""; + var plist = protocols.split(','); + if (plist.indexOf('binary') >= 0) { + callback(true, 'binary'); + } else if (plist.indexOf('base64') >= 0) { + callback(true, 'base64'); + } else { + console.log("Client must support 'binary' or 'base64' protocol"); + callback(false); + } +} + // parse source and target arguments into parts try { source_arg = argv._[0].toString(); @@ -120,7 +165,7 @@ try { throw("illegal port"); } } catch(e) { - console.error("wsproxy.py [--web web_dir] [source_addr:]source_port target_addr:target_port"); + console.error("websockify.js [--web web_dir] [--cert cert.pem [--key key.pem]] [source_addr:]source_port target_addr:target_port"); process.exit(2); } @@ -131,11 +176,21 @@ if (argv.web) { console.log(" - Web server active. Serving: " + argv.web); } -httpServer = http.createServer(http_request); -httpServer.listen(source_port, function() { - wsServer = new WebSocketServer({server: httpServer}); +if (argv.cert) { + argv.key = argv.key || argv.cert; + var cert = fs.readFileSync(argv.cert), + key = fs.readFileSync(argv.key); + console.log(" - Running in encrypted HTTPS (wss://) mode using: " + argv.cert + ", " + argv.key); + webServer = https.createServer({cert: cert, key: key}, http_request); +} else { + console.log(" - Running in unencrypted HTTP (ws://) mode"); + webServer = http.createServer(http_request); +} +webServer.listen(source_port, function() { + wsServer = new WebSocketServer({server: webServer, + handleProtocols: selectProtocol}); wsServer.on('connection', new_client); }); // Attach Flash policyfile answer service -policyfile.createServer().listen(-1, httpServer); +policyfile.createServer().listen(-1, webServer); diff --git a/third_party/websockify/setup.py b/third_party/websockify/setup.py index 947f0a52..ebf5e610 100644 --- a/third_party/websockify/setup.py +++ b/third_party/websockify/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_packages -version = '0.1.0' +version = '0.2.1' name = 'websockify' long_description = open("README.md").read() + "\n" + \ open("CHANGES.txt").read() + "\n" diff --git a/third_party/websockify/websockify/websocket.py b/third_party/websockify/websockify/websocket.py index 4c0cacc9..1a5b9ffa 100644 --- a/third_party/websockify/websockify/websocket.py +++ b/third_party/websockify/websockify/websocket.py @@ -240,32 +240,33 @@ Sec-WebSocket-Accept: %s\r os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno()) @staticmethod - def unmask(buf, f): - pstart = f['hlen'] + 4 - pend = pstart + f['length'] + def unmask(buf, hlen, plen): + pstart = hlen + 4 + pend = pstart + plen if numpy: b = c = s2b('') - if f['length'] >= 4: + if plen >= 4: mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'), - offset=f['hlen'], count=1) + offset=hlen, count=1) data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'), - offset=pstart, count=int(f['length'] / 4)) + offset=pstart, count=int(plen / 4)) #b = numpy.bitwise_xor(data, mask).data b = numpy.bitwise_xor(data, mask).tostring() - if f['length'] % 4: + if plen % 4: #print("Partial unmask") mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'), - offset=f['hlen'], count=(f['length'] % 4)) + offset=hlen, count=(plen % 4)) data = numpy.frombuffer(buf, dtype=numpy.dtype('B'), - offset=pend - (f['length'] % 4), - count=(f['length'] % 4)) + offset=pend - (plen % 4), + count=(plen % 4)) c = numpy.bitwise_xor(data, mask).tostring() return b + c else: # Slower fallback + mask = buf[hlen:hlen+4] data = array.array('B') - mask = s2a(f['mask']) + mask = s2a(mask) data.fromstring(buf[pstart:pend]) for i in range(len(data)): data[i] ^= mask[i % 4] @@ -304,7 +305,7 @@ Sec-WebSocket-Accept: %s\r Returns: {'fin' : 0_or_1, 'opcode' : number, - 'mask' : 32_bit_number, + 'masked' : boolean, 'hlen' : header_bytes_number, 'length' : payload_bytes_number, 'payload' : decoded_buffer, @@ -315,7 +316,7 @@ Sec-WebSocket-Accept: %s\r f = {'fin' : 0, 'opcode' : 0, - 'mask' : 0, + 'masked' : False, 'hlen' : 2, 'length' : 0, 'payload' : None, @@ -332,7 +333,7 @@ Sec-WebSocket-Accept: %s\r b1, b2 = unpack_from(">BB", buf) f['opcode'] = b1 & 0x0f f['fin'] = (b1 & 0x80) >> 7 - has_mask = (b2 & 0x80) >> 7 + f['masked'] = (b2 & 0x80) >> 7 f['length'] = b2 & 0x7f @@ -347,7 +348,7 @@ Sec-WebSocket-Accept: %s\r return f # Incomplete frame header (f['length'],) = unpack_from('>xxQ', buf) - full_len = f['hlen'] + has_mask * 4 + f['length'] + full_len = f['hlen'] + f['masked'] * 4 + f['length'] if blen < full_len: # Incomplete frame return f # Incomplete frame header @@ -356,13 +357,13 @@ Sec-WebSocket-Accept: %s\r f['left'] = blen - full_len # Process 1 frame - if has_mask: + if f['masked']: # unmask payload - f['mask'] = buf[f['hlen']:f['hlen']+4] - f['payload'] = WebSocketServer.unmask(buf, f) + f['payload'] = WebSocketServer.unmask(buf, f['hlen'], + f['length']) else: print("Unmasked frame: %s" % repr(buf)) - f['payload'] = buf[(f['hlen'] + has_mask * 4):full_len] + f['payload'] = buf[(f['hlen'] + f['masked'] * 4):full_len] if base64 and f['opcode'] in [1, 2]: try: @@ -389,6 +390,7 @@ Sec-WebSocket-Accept: %s\r end = buf.find(s2b('\xff')) return {'payload': b64decode(buf[1:end]), 'hlen': 1, + 'masked': False, 'length': end - 1, 'left': len(buf) - (end + 1)} @@ -456,7 +458,7 @@ Sec-WebSocket-Accept: %s\r if self.rec: self.rec.write("%s,\n" % repr("{%s{" % tdelta - + encbuf[lenhead:-lentail])) + + encbuf[lenhead:len(encbuf)-lentail])) self.send_parts.append(encbuf) @@ -536,8 +538,14 @@ Sec-WebSocket-Accept: %s\r if self.rec: start = frame['hlen'] end = frame['hlen'] + frame['length'] + if frame['masked']: + recbuf = WebSocketServer.unmask(buf, frame['hlen'], + frame['length']) + else: + recbuf = buf[frame['hlen']:frame['hlen'] + + frame['length']] self.rec.write("%s,\n" % - repr("}%s}" % tdelta + buf[start:end])) + repr("}%s}" % tdelta + recbuf)) bufs.append(frame['payload']) @@ -779,6 +787,10 @@ Sec-WebSocket-Accept: %s\r self.handler_id) self.msg("opening record file: %s" % fname) self.rec = open(fname, 'w+') + encoding = "binary" + if self.base64: encoding = "base64" + self.rec.write("var VNC_frame_encoding = '%s';\n" + % encoding) self.rec.write("var VNC_frame_data = [\n") self.ws_connection = True @@ -800,7 +812,7 @@ Sec-WebSocket-Accept: %s\r self.msg(traceback.format_exc()) finally: if self.rec: - self.rec.write("'EOF']\n") + self.rec.write("'EOF'];\n") self.rec.close() if self.client and self.client != startsock: diff --git a/third_party/websockify/websockify/websocketproxy.py b/third_party/websockify/websockify/websocketproxy.py index 8bd67ec4..1154d925 100755 --- a/third_party/websockify/websockify/websocketproxy.py +++ b/third_party/websockify/websockify/websocketproxy.py @@ -14,8 +14,11 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates import signal, socket, optparse, time, os, sys, subprocess from select import select import websocket -try: from urllib.parse import parse_qs, urlparse -except: from urlparse import parse_qs, urlparse +try: + from urllib.parse import parse_qs, urlparse +except: + from cgi import parse_qs + from urlparse import urlparse class WebSocketProxy(websocket.WebSocketServer): """ @@ -205,7 +208,7 @@ Traffic Legend: # Extract the token parameter from url args = parse_qs(urlparse(path)[4]) # 4 is the query from url - if not len(args['token']): + if not args.has_key('token') or not len(args['token']): raise self.EClose("Token not present") token = args['token'][0].rstrip('\n') @@ -249,6 +252,24 @@ Traffic Legend: ins, outs, excepts = select(rlist, wlist, [], 1) if excepts: raise Exception("Socket exception") + if self.client in outs: + # Send queued target data to the client + c_pend = self.send_frames(cqueue) + + cqueue = [] + + if self.client in ins: + # Receive client data, decode it, and queue for target + bufs, closed = self.recv_frames() + tqueue.extend(bufs) + + if closed: + # TODO: What about blocking on client socket? + self.vmsg("%s:%s: Client closed connection" %( + self.target_host, self.target_port)) + raise self.CClose(closed['code'], closed['reason']) + + if target in outs: # Send queued client data to the target dat = tqueue.pop(0) @@ -273,24 +294,6 @@ Traffic Legend: self.traffic("{") - if self.client in outs: - # Send queued target data to the client - c_pend = self.send_frames(cqueue) - - cqueue = [] - - - if self.client in ins: - # Receive client data, decode it, and queue for target - bufs, closed = self.recv_frames() - tqueue.extend(bufs) - - if closed: - # TODO: What about blocking on client socket? - self.vmsg("%s:%s: Client closed connection" %( - self.target_host, self.target_port)) - raise self.CClose(closed['code'], closed['reason']) - def _subprocess_setup(): # Python installs a SIGPIPE handler by default. This is usually not what |