diff --git a/LICENSE.txt b/LICENSE.txt index f1648de..cca6550 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -3,8 +3,6 @@ docs/LICENSE.LGPL-3) with the following exceptions: 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 diff --git a/README.md b/README.md index 5399032..1dd0f21 100644 --- a/README.md +++ b/README.md @@ -33,16 +33,8 @@ href="http://www.twitter.com/noVNC">@noVNC if you do. ### WebSockets binary data Starting with websockify 0.5.0, only the HyBi / IETF -6455 WebSocket protocol is supported. - -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. +6455 WebSocket protocol is supported. There is no support for the older +Base64 encoded data format. ### Encrypted WebSocket connections (wss://) @@ -77,8 +69,7 @@ and then also to the key with `--key`. Finally, use `--ssl-only` as needed. The `include/websock.js` Javascript library library provides a Websock object that 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. +via websockify. Websock has built-in receive queue buffering; the message event does not contain actual data but is simply a notification that diff --git a/include/base64.js b/include/base64.js deleted file mode 100644 index 8733612..0000000 --- a/include/base64.js +++ /dev/null @@ -1,114 +0,0 @@ -/* 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 */ diff --git a/include/websock.js b/include/websock.js index 0e4718a..94ec1c4 100644 --- a/include/websock.js +++ b/include/websock.js @@ -5,8 +5,7 @@ * * 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. + * via websockify. * * Websock has built-in receive queue buffering; the message event * does not contain actual data but is simply a notification that @@ -15,7 +14,7 @@ */ /*jslint browser: true, bitwise: false, plusplus: false */ -/*global Util, Base64 */ +/*global Util */ // Load Flash WebSocket emulator if needed @@ -52,7 +51,6 @@ function Websock() { 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 @@ -154,36 +152,6 @@ function rQwait(msg, num, goback) { 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 // @@ -196,7 +164,7 @@ function flush() { //Util.Debug("arr: " + arr); //Util.Debug("sQ: " + sQ); if (sQ.length > 0) { - websocket.send(encode_message(sQ)); + websocket.send((new Uint8Array(sQ)).buffer); sQ = []; } return true; @@ -227,7 +195,10 @@ function recv_message(e) { //Util.Debug(">> recv_message: " + e.data.length); try { - decode_message(e.data); + var u8 = new Uint8Array(e.data); + for (var i = 0; i < u8.length; i++) { + rQ.push(u8[i]); + } if (rQlen() > 0) { eventHandlers.message(); // Compact the receive queue @@ -262,92 +233,27 @@ function on(evt, handler) { eventHandlers[evt] = handler; } -function init(protocols, ws_schema) { +function init() { 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); + init(); if (test_mode) { websocket = {}; } else { websocket = new WebSocket(uri, protocols); - if (protocols.indexOf('binary') >= 0) { - websocket.binaryType = 'arraybuffer'; - } + 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"); }; @@ -376,9 +282,8 @@ function close() { // Override internal functions for testing // Takes a send function, returns reference to recv function -function testMode(override_send, data_mode) { +function testMode(override_send) { test_mode = true; - mode = data_mode; api.send = override_send; api.close = function () {}; return recv_message; diff --git a/other/js/websockify.js b/other/js/websockify.js index 0f6c652..7998dde 100755 --- a/other/js/websockify.js +++ b/other/js/websockify.js @@ -42,11 +42,7 @@ new_client = function(client) { target.on('data', function(data) { //log("sending message: " + data); try { - if (client.protocol === 'base64') { - client.send(new Buffer(data).toString('base64')); - } else { - client.send(data,{binary: true}); - } + client.send(data); } catch(e) { log("Client closed, cleaning up target"); target.end(); @@ -64,11 +60,7 @@ new_client = function(client) { client.on('message', function(msg) { //log('got message: ' + msg); - if (client.protocol === 'base64') { - target.write(new Buffer(msg, 'base64')); - } else { - target.write(msg,'binary'); - } + target.write(msg); }); client.on('close', function(code, reason) { log('WebSocket client disconnected: ' + code + ' [' + reason + ']'); @@ -123,18 +115,6 @@ http_request = function (request, response) { }); }; -// Select 'binary' or 'base64' subprotocol, preferring 'binary' -selectProtocol = function(protocols, callback) { - if (protocols.indexOf('binary') >= 0) { - callback(true, 'binary'); - } else if (protocols.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(); @@ -183,8 +163,7 @@ if (argv.cert) { webServer = http.createServer(http_request); } webServer.listen(source_port, function() { - wsServer = new WebSocketServer({server: webServer, - handleProtocols: selectProtocol}); + wsServer = new WebSocketServer({server: webServer}); wsServer.on('connection', new_client); }); diff --git a/other/websocket.rb b/other/websocket.rb index bb287ea..ef15b28 100644 --- a/other/websocket.rb +++ b/other/websocket.rb @@ -13,7 +13,6 @@ require 'openssl' require 'stringio' require 'digest/md5' require 'digest/sha1' -require 'base64' unless OpenSSL::SSL::SSLSocket.instance_methods.index("read_nonblock") module OpenSSL @@ -92,7 +91,6 @@ Sec-WebSocket-Accept: %s\r t[:my_client_id] = @@client_id t[:send_parts] = [] t[:recv_part] = nil - t[:base64] = nil puts "in serve, client: #{t[:my_client_id].inspect}" @@ -151,11 +149,7 @@ Sec-WebSocket-Accept: %s\r return data end - def encode_hybi(buf, opcode, base64=false) - if base64 - buf = Base64.encode64(buf).gsub(/\n/, '') - end - + def encode_hybi(buf, opcode) b1 = 0x80 | (opcode & 0x0f) # FIN + opcode payload_len = buf.length if payload_len <= 125 @@ -170,7 +164,7 @@ Sec-WebSocket-Accept: %s\r return [header + buf, header.length, 0] end - def decode_hybi(buf, base64=false) + def decode_hybi(buf) f = {'fin' => 0, 'opcode' => 0, 'hlen' => 2, @@ -216,10 +210,6 @@ Sec-WebSocket-Accept: %s\r f['payload'] = buf[f['hlen']...full_len] end - if base64 and [1, 2].include?(f['opcode']) - f['payload'] = Base64.decode64(f['payload']) - end - # close frame if f['opcode'] == 0x08 if f['length'] >= 2 @@ -251,13 +241,8 @@ Sec-WebSocket-Accept: %s\r encbuf = "" bufs.each do |buf| if t[:version].start_with?("hybi") - if t[:base64] - encbuf, lenhead, lentail = encode_hybi( - buf, opcode=1, base64=true) - else - encbuf, lenhead, lentail = encode_hybi( - buf, opcode=2, base64=false) - end + encbuf, lenhead, lentail = encode_hybi( + buf, opcode=2) else encbuf, lenhead, lentail = encode_hixie(buf) end @@ -302,7 +287,7 @@ Sec-WebSocket-Accept: %s\r while buf.length > 0 if t[:version].start_with?("hybi") - frame = decode_hybi(buf, base64=t[:base64]) + frame = decode_hybi(buf) if frame['payload'] == nil traffic "}." @@ -359,7 +344,7 @@ Sec-WebSocket-Accept: %s\r msg = [reason.length, code].pack("na8") end - buf, lenh, lent = encode_hybi(msg, opcode=0x08, base64=false) + buf, lenh, lent = encode_hybi(msg, opcode=0x08) t[:client].write(buf) elsif t[:version] == "hixie-76" buf = "\xff\x00" @@ -412,73 +397,29 @@ Sec-WebSocket-Accept: %s\r protocols = h.fetch("sec-websocket-protocol", h["websocket-protocol"]) ver = h.fetch('sec-websocket-version', nil) - if ver - # HyBi/IETF vesrion of the protocol - - # HyBi 07 reports version 7 - # HyBi 08 - 12 report version 8 - # HyBi 13 and up report version 13 - if ['7', '8', '13'].include?(ver) - t[:version] = "hybi-%02d" % [ver.to_i] - else - raise EClose, "Unsupported protocol version %s" % [ver] - end - - # choose binary if client supports it - if protocols.include?('binary') - t[:base64] = false - elsif protocols.include?('base64') - t[:base64] = true - else - raise EClose, "Client must support 'binary' or 'base64' sub-protocol" - end - - key = h['sec-websocket-key'] - - # Generate the hash value for the accpet header - accept = Base64.encode64( - Digest::SHA1.digest(key + @@GUID)).gsub(/\n/, '') - - response = @@Server_handshake_hybi % [accept] - - if t[:base64] - response += "Sec-WebSocket-Protocol: base64\r\n" - else - response += "Sec-WebSocket-Protocol: binary\r\n" - end - response += "\r\n" + # HyBi/IETF vesrion of the protocol + # HyBi 07 reports version 7 + # HyBi 08 - 12 report version 8 + # HyBi 13 and up report version 13 + if ['7', '8', '13'].include?(ver) + t[:version] = "hybi-%02d" % [ver.to_i] else - # Hixie vesrion of the protocol (75 or 76) - body = handshake.match(/\r\n\r\n(........)/) - if body - h['key3'] = body[1] - trailer = gen_md5(h) - pre = "Sec-" - t[:version] = "hixie-76" - else - trailer = "" - pre = "" - t[:version] = "hixie-75" - end - - # base64 required for Hixie since payload is only UTF-8 - t[:base64] = true - - response = @@Server_handshake_hixie % [pre, h['origin'], pre, - "ws", h['host'], t[:path]] - - if protocols && protocols.include?('base64') - response += "%sWebSocket-Protocol: base64\r\n" % [pre] - else - msg "Warning: client does not report 'base64' protocol support" - end - - response += "\r\n" + trailer + raise EClose, "Unsupported protocol version %s" % [ver] end + key = h['sec-websocket-key'] + + # Generate the hash value for the accpet header + accept = Base64.encode64( + Digest::SHA1.digest(key + @@GUID)).gsub(/\n/, '') + + response = @@Server_handshake_hybi % [accept] + + response += "\r\n" + msg "%s WebSocket connection" % [stype] - msg "Version %s, base64: '%s'" % [t[:version], t[:base64]] + msg "Version %s" % [t[:version]] if t[:path] then msg "Path: '%s'" % [t[:path]] end #puts "sending reponse #{response.inspect}" diff --git a/tests/b64_vs_utf8.py b/tests/b64_vs_utf8.py deleted file mode 100755 index 9af7b62..0000000 --- a/tests/b64_vs_utf8.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python - -from base64 import b64encode, b64decode -from codecs import (utf_8_encode, utf_8_decode, - latin_1_encode, latin_1_decode) -import random, time - -buf_len = 10000 -iterations = 10000 - -print "Generating random input buffer" -r = random.Random() -buf = "".join([chr(r.randint(0, 255)) for i in range(buf_len)]) - -tests = {'UTF8 encode': lambda: utf_8_encode(unicode(buf, 'latin-1'))[0], - 'B64 encode': lambda: b64encode(buf)} -utf8_buf = tests['UTF8 encode']() -b64_buf = tests['B64 encode']() -tests.update({'UTF8 decode': lambda: latin_1_encode(utf_8_decode(utf8_buf)[0])[0], - 'B64 decode': lambda: b64decode(b64_buf)}) - -print "Running tests" -for test in 'UTF8 encode', 'B64 encode', 'UTF8 decode', 'B64 decode': - start = time.time() - for i in range(iterations): - res_buf = tests[test]() - print "%s took %s seconds (result size %s)" % ( - test, (time.time() - start), len(res_buf)) - diff --git a/tests/base64.html b/tests/base64.html deleted file mode 100644 index 24ad80b..0000000 --- a/tests/base64.html +++ /dev/null @@ -1,91 +0,0 @@ - - - - Native Base64 Tests - - - - - -

Native Base64 Tests

- -
- Messages:
- - -
- - - diff --git a/tests/base64.js b/tests/base64.js deleted file mode 100644 index 6ade00a..0000000 --- a/tests/base64.js +++ /dev/null @@ -1,12 +0,0 @@ -// The following results in 'hello [MANGLED]' -// -// Filed as https://github.com/ry/node/issues/issue/402 - -var sys = require("sys"), - buf = new Buffer(1024), len, - str1 = "aGVsbG8g", // 'hello ' - str2 = "d29ybGQ=", // 'world' - -len = buf.write(str1, 0, 'base64'); -len += buf.write(str2, len, 'base64'); -sys.log("decoded result: " + buf.toString('binary', 0, len)); diff --git a/tests/echo.html b/tests/echo.html index cc8b642..8cc7ecb 100644 --- a/tests/echo.html +++ b/tests/echo.html @@ -4,7 +4,6 @@ WebSockets Echo Test -