mirror of
https://github.com/fcwu/docker-ubuntu-vnc-desktop
synced 2026-08-05 16:12:41 +02:00
feat: upgrade noVNC
This commit is contained in:
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env perl
|
||||
use MIME::Base64;
|
||||
|
||||
for (<>) {
|
||||
unless (/^'([{}])(\d+)\1(.+?)',$/) {
|
||||
print;
|
||||
next;
|
||||
}
|
||||
|
||||
my ($dir, $amt, $b64) = ($1, $2, $3);
|
||||
|
||||
my $decoded = MIME::Base64::decode($b64) or die "Could not base64-decode line `$_`";
|
||||
|
||||
my $decoded_escaped = join "", map { "\\x$_" } unpack("(H2)*", $decoded);
|
||||
|
||||
print "'${dir}${amt}${dir}${decoded_escaped}',\n";
|
||||
}
|
||||
@@ -7,7 +7,7 @@ Licensed under MPL-2.0 (see docs/LICENSE.MPL-2.0)
|
||||
'''
|
||||
|
||||
# a bar plot with errorbars
|
||||
import sys, json, pprint
|
||||
import sys, json
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.font_manager import FontProperties
|
||||
|
||||
+18
-5
@@ -1,11 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2016 Joel Martin
|
||||
# Copyright 2016 Solly Ross
|
||||
# Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
||||
|
||||
usage() {
|
||||
if [ "$*" ]; then
|
||||
echo "$*"
|
||||
echo
|
||||
fi
|
||||
echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT]"
|
||||
echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]"
|
||||
echo
|
||||
echo "Starts the WebSockets proxy and a mini-webserver and "
|
||||
echo "provides a cut-and-paste URL to go to."
|
||||
@@ -18,6 +22,8 @@ usage() {
|
||||
echo " Default: self.pem"
|
||||
echo " --web WEB Path to web files (e.g. vnc.html)"
|
||||
echo " Default: ./"
|
||||
echo " --ssl-only Disable non-https connections."
|
||||
echo " "
|
||||
exit 2
|
||||
}
|
||||
|
||||
@@ -29,6 +35,7 @@ VNC_DEST="localhost:5900"
|
||||
CERT=""
|
||||
WEB=""
|
||||
proxy_pid=""
|
||||
SSLONLY=""
|
||||
|
||||
die() {
|
||||
echo "$*"
|
||||
@@ -55,6 +62,7 @@ while [ "$*" ]; do
|
||||
--vnc) VNC_DEST="${OPTARG}"; shift ;;
|
||||
--cert) CERT="${OPTARG}"; shift ;;
|
||||
--web) WEB="${OPTARG}"; shift ;;
|
||||
--ssl-only) SSLONLY="--ssl-only" ;;
|
||||
-h|--help) usage ;;
|
||||
-*) usage "Unknown chrooter option: ${param}" ;;
|
||||
*) break ;;
|
||||
@@ -65,7 +73,7 @@ done
|
||||
which netstat >/dev/null 2>&1 \
|
||||
|| die "Must have netstat installed"
|
||||
|
||||
netstat -ltn | grep -qs "${PORT} .*LISTEN" \
|
||||
netstat -ltn | grep -qs ":${PORT} .*LISTEN" \
|
||||
&& die "Port ${PORT} in use. Try --listen PORT"
|
||||
|
||||
trap "cleanup" TERM QUIT INT EXIT
|
||||
@@ -108,7 +116,7 @@ if [[ -e ${HERE}/websockify ]]; then
|
||||
|
||||
if [[ ! -x $WEBSOCKIFY ]]; then
|
||||
echo "The path ${HERE}/websockify exists, but $WEBSOCKIFY either does not exist or is not executable."
|
||||
echo "If you inteded to use an installed websockify package, please remove ${HERE}/websockify."
|
||||
echo "If you intended to use an installed websockify package, please remove ${HERE}/websockify."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -134,7 +142,7 @@ fi
|
||||
|
||||
echo "Starting webserver and WebSockets proxy on port ${PORT}"
|
||||
#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
|
||||
${WEBSOCKIFY} --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
|
||||
${WEBSOCKIFY} ${SSLONLY} --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
|
||||
proxy_pid="$!"
|
||||
sleep 1
|
||||
if ! ps -p ${proxy_pid} >/dev/null; then
|
||||
@@ -144,7 +152,12 @@ if ! ps -p ${proxy_pid} >/dev/null; then
|
||||
fi
|
||||
|
||||
echo -e "\n\nNavigate to this URL:\n"
|
||||
echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
|
||||
if [ "x$SSLONLY" == "x" ]; then
|
||||
echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
|
||||
else
|
||||
echo -e " https://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
|
||||
fi
|
||||
|
||||
echo -e "Press Ctrl-C to exit\n\n"
|
||||
|
||||
wait ${proxy_pid}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
var through = require('through2');
|
||||
|
||||
var singleLineRe = /\/\* \[module\] ((.(?!\*\/))+) \*\//g;
|
||||
var multiLineRe = /\/\* \[module\]\n(( * .+\n)+) \*\//g;
|
||||
|
||||
var skipAsModule = /\/\* \[begin skip-as-module\] \*\/(.|\n)+\/\* \[end skip-as-module\] \*\//g;
|
||||
|
||||
module.exports = function (file) {
|
||||
var stream = through(function (buf, enc, next) {
|
||||
var bufStr = buf.toString('utf8');
|
||||
bufStr = bufStr.replace(singleLineRe, "$1");
|
||||
bufStr = bufStr.replace(multiLineRe, function (match, mainLines) {
|
||||
return mainLines.split(" * ").join("");
|
||||
});
|
||||
|
||||
bufStr = bufStr.replace(skipAsModule, "");
|
||||
|
||||
this.push(bufStr);
|
||||
next();
|
||||
});
|
||||
|
||||
stream._is_make_module = true;
|
||||
|
||||
return stream;
|
||||
};
|
||||
@@ -87,7 +87,13 @@ var out = "// This file describes mappings from Unicode codepoints to the keysym
|
||||
"\n" +
|
||||
" function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; }\n" +
|
||||
" return {\n" +
|
||||
" fromUnicode : function(u) { return lookup(codepoints[u]); },\n" +
|
||||
" fromUnicode : function(u) {\n" +
|
||||
" var keysym = codepoints[u];\n" +
|
||||
" if (keysym === undefined) {\n" +
|
||||
" keysym = 0x01000000 | u;\n" +
|
||||
" }\n" +
|
||||
" return lookup(keysym);\n" +
|
||||
" },\n" +
|
||||
" lookup : lookup\n" +
|
||||
" };\n" +
|
||||
"})();\n";
|
||||
|
||||
Executable
+121
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var path = require('path');
|
||||
var program = require('commander');
|
||||
var fs = require('fs');
|
||||
var fse = require('fs-extra');
|
||||
var browserify = require('browserify');
|
||||
|
||||
var make_modules_transform = require('./make-module-transform');
|
||||
var babelify = require("babelify");
|
||||
|
||||
|
||||
program
|
||||
.option('-b, --browserify', 'create a browserify bundled app')
|
||||
.option('--as-require', 'output files using "require" instead of ES6 import and export')
|
||||
.parse(process.argv);
|
||||
|
||||
// the various important paths
|
||||
var core_path = path.resolve(__dirname, '..', 'core');
|
||||
var app_path = path.resolve(__dirname, '..', 'app');
|
||||
var out_dir_base = path.resolve(__dirname, '..', 'build');
|
||||
var lib_dir_base = path.resolve(__dirname, '..', 'lib');
|
||||
|
||||
var make_browserify = function (src_files, opts) {
|
||||
// change to the root noVNC directory
|
||||
process.chdir(path.resolve(__dirname, '..'));
|
||||
|
||||
var b = browserify(src_files, opts);
|
||||
|
||||
// register the transforms
|
||||
b.transform(make_modules_transform);
|
||||
b.transform(babelify,
|
||||
{ plugins: ["add-module-exports", "transform-es2015-modules-commonjs"] });
|
||||
|
||||
return b;
|
||||
};
|
||||
|
||||
var make_full_app = function () {
|
||||
// make sure the output directory exists
|
||||
fse.ensureDir(out_dir_base);
|
||||
|
||||
// actually bundle the files into a browserified bundled
|
||||
var ui_file = path.join(app_path, 'ui.js');
|
||||
var b = make_browserify(ui_file, {});
|
||||
var app_file = path.join(out_dir_base, 'app.js');
|
||||
b.bundle().pipe(fs.createWriteStream(app_file));
|
||||
|
||||
// copy over app-related resources (images, styles, etc)
|
||||
var src_dir_app = path.join(__dirname, '..', 'app');
|
||||
fs.readdir(src_dir_app, function (err, files) {
|
||||
if (err) { throw err; }
|
||||
|
||||
files.forEach(function (src_file) {
|
||||
var src_file_path = path.resolve(src_dir_app, src_file);
|
||||
var out_file_path = path.resolve(out_dir_base, src_file);
|
||||
var ext = path.extname(src_file);
|
||||
if (ext === '.js' || ext === '.html') return;
|
||||
fse.copy(src_file_path, out_file_path, function (err) {
|
||||
if (err) { throw err; }
|
||||
console.log("Copied file(s) from " + src_file_path + " to " + out_file_path);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// write out the modified vnc.html file that works with the bundle
|
||||
var src_html_path = path.resolve(__dirname, '..', 'vnc.html');
|
||||
var out_html_path = path.resolve(out_dir_base, 'vnc.html');
|
||||
fs.readFile(src_html_path, function (err, contents_raw) {
|
||||
if (err) { throw err; }
|
||||
|
||||
var contents = contents_raw.toString();
|
||||
contents = contents.replace(/="app\//g, '="');
|
||||
|
||||
var start_marker = '<!-- begin scripts -->\n';
|
||||
var end_marker = '<!-- end scripts -->';
|
||||
var start_ind = contents.indexOf(start_marker) + start_marker.length;
|
||||
var end_ind = contents.indexOf(end_marker, start_ind);
|
||||
|
||||
contents = contents.slice(0, start_ind) + '<script src="app.js"></script>\n' + contents.slice(end_ind);
|
||||
|
||||
fs.writeFile(out_html_path, contents, function (err) {
|
||||
if (err) { throw err; }
|
||||
console.log("Wrote " + out_html_path);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var make_lib_files = function (use_require) {
|
||||
// make sure the output directory exists
|
||||
fse.ensureDir(lib_dir_base);
|
||||
|
||||
var through = require('through2');
|
||||
|
||||
var deps = {};
|
||||
var rfb_file = path.join(core_path, 'rfb.js');
|
||||
var b = make_browserify(rfb_file, {});
|
||||
b.on('transform', function (tr, file) {
|
||||
if (tr._is_make_module) {
|
||||
var new_path = path.join(lib_dir_base, path.relative(core_path, file));
|
||||
fse.ensureDir(path.dirname(new_path));
|
||||
console.log("Writing " + new_path)
|
||||
var fileStream = fs.createWriteStream(new_path);
|
||||
|
||||
if (use_require) {
|
||||
var babelificate = babelify(file,
|
||||
{ plugins: ["add-module-exports", "transform-es2015-modules-commonjs"] });
|
||||
tr.pipe(babelificate);
|
||||
tr = babelificate;
|
||||
}
|
||||
tr.pipe(fileStream);
|
||||
}
|
||||
});
|
||||
|
||||
b.bundle();
|
||||
};
|
||||
|
||||
if (program.browserify) {
|
||||
make_full_app();
|
||||
} else {
|
||||
make_lib_files(program.asRequire);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
language: python
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
- 3.3
|
||||
- 3.4
|
||||
|
||||
install: pip install -r test-requirements.txt
|
||||
|
||||
script: python setup.py nosetests --verbosity=3
|
||||
@@ -1,6 +1,34 @@
|
||||
Changes
|
||||
=======
|
||||
|
||||
0.8.0
|
||||
-----
|
||||
|
||||
* Make websockify properly terminate children on SIGTERM (#226)
|
||||
* Remove logging in signal handlers (this can cause Python to hang under certain conditions) (#219)
|
||||
* Make it easier to log to a file (#205)
|
||||
* Add support for IPv6 addresses in tokens in the TokenFile token plugins (#197)
|
||||
* Improve auth plugin framework to enable better support for HTTP auth (#194, #201)
|
||||
* Fix bug in JSONTokenAPI token plugin (#192)
|
||||
* Fix a missing variable in the exception handler (#178)
|
||||
|
||||
0.7.0
|
||||
-----
|
||||
|
||||
* Python 3 support fixes (#140, #155, #159)
|
||||
* Generic token-parsing plugins support (#162)
|
||||
* Generic authentication plugins support (#172)
|
||||
* Fixed frame corruption on big-endian systems (#161)
|
||||
* Support heartbeats (via PING) and automatic responses to PONG (#169)
|
||||
* Automatically reject unmasked client frames by default (strict mode) (#174)
|
||||
* Automatically restart interrupted select calls (#175)
|
||||
* Make 'run' respect environment settings (including virtualenv) (#176)
|
||||
|
||||
0.6.1 - May 11, 2015
|
||||
--------------------
|
||||
|
||||
* **PATCH RELEASE**: Fixes a bug causing file_only to not be passed properly
|
||||
|
||||
0.6.0 - Feb 18, 2014
|
||||
--------------------
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -33,31 +33,35 @@ href="http://www.twitter.com/noVNC">@noVNC</a> 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://)
|
||||
|
||||
To encrypt the traffic using the WebSocket 'wss://' URI scheme you
|
||||
need to generate a certificate for websockify to load. By default websockify
|
||||
loads a certificate file name `self.pem` but the `--cert=CERT` option can
|
||||
override the file name. You can generate a self-signed certificate using
|
||||
openssl. When asked for the common name, use the hostname of the server where
|
||||
the proxy will be running:
|
||||
To encrypt the traffic using the WebSocket 'wss://' URI scheme you need to
|
||||
generate a certificate and key for Websockify to load. By default, Websockify
|
||||
loads a certificate file name `self.pem` but the `--cert=CERT` and `--key=KEY`
|
||||
options can override the file name. You can generate a self-signed certificate
|
||||
using openssl. When asked for the common name, use the hostname of the server
|
||||
where the proxy will be running:
|
||||
|
||||
```
|
||||
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
||||
```
|
||||
|
||||
For a self-signed certificate to work, you need to make your client/browser
|
||||
understand it. You can do this by installing it as accepted certificate, or by
|
||||
using that same certificate for a HTTPS connection to which you navigate first
|
||||
and approve. Browsers generally don't give you the "trust certificate?" prompt
|
||||
by opening a WSS socket with invalid certificate, hence you need to have it
|
||||
acccept it by either of those two methods.
|
||||
|
||||
If you have a commercial/valid SSL certificate with one ore more intermediate
|
||||
certificates, concat them into one file, server certificate first, then the
|
||||
intermediate(s) from the CA, etc. Point to this file with the `--cert` option
|
||||
and then also to the key with `--key`. Finally, use `--ssl-only` as needed.
|
||||
|
||||
|
||||
### Websock Javascript library
|
||||
|
||||
@@ -65,8 +69,7 @@ openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
||||
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
|
||||
@@ -91,23 +94,20 @@ These are not necessary for the basic operation.
|
||||
wrapping the socket if the data starts with '\x16' or '\x80'
|
||||
(indicating SSL).
|
||||
|
||||
* Flash security policy: websockify detects flash security policy
|
||||
requests (again by sniffing the first packet) and answers with an
|
||||
appropriate flash security policy response (and then closes the
|
||||
port). This means no separate flash security policy server is needed
|
||||
for supporting the flash WebSockets fallback emulator.
|
||||
|
||||
* Session recording: This feature that allows recording of the traffic
|
||||
sent and received from the client to a file using the `--record`
|
||||
option.
|
||||
|
||||
* Mini-webserver: websockify can detect and respond to normal web
|
||||
requests on the same port as the WebSockets proxy and Flash security
|
||||
policy. This functionality is activate with the `--web DIR` option
|
||||
where DIR is the root of the web directory to serve.
|
||||
requests on the same port as the WebSockets proxy. This functionality
|
||||
is activated with the `--web DIR` option where DIR is the root of the
|
||||
web directory to serve.
|
||||
|
||||
* Wrap a program: see the "Wrap a Program" section below.
|
||||
|
||||
* Log files: websockify can save all logging information in a file.
|
||||
This functionality is activated with the `--log-file FILE` option
|
||||
where FILE is the file where the logs should be saved.
|
||||
|
||||
### Implementations of websockify
|
||||
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
Some implementation notes:
|
||||
|
||||
There is an included flash object (web-socket-js) that is used to
|
||||
emulate websocket support on browsers without websocket support
|
||||
(currently only Chrome has WebSocket support).
|
||||
|
||||
Javascript doesn't have a bytearray type, so what you get out of
|
||||
a WebSocket object is just Javascript strings. Javascript has UTF-16
|
||||
unicode strings and anything sent through the WebSocket gets converted
|
||||
to UTF-8 and vice-versa. So, one additional (and necessary) function
|
||||
of wsproxy is base64 encoding/decoding what is sent to/from the
|
||||
browser.
|
||||
|
||||
Building web-socket-js emulator:
|
||||
|
||||
cd include/web-socket-js/flash-src
|
||||
mxmlc -static-link-runtime-shared-libraries WebSocketMain.as
|
||||
|
||||
Building release tarball:
|
||||
- not really necessary since tagged revision can be downloaded
|
||||
from github as tarballs
|
||||
|
||||
@@ -64,14 +64,11 @@ Daemonizing: When the -D option is specified, websockify runs in the background
|
||||
.IP *
|
||||
SSL (the wss:// WebSockets URI): This is detected automatically by websockify by sniffing the first byte sent from the client and then wrapping the socket if the data starts with '\\x16' or '\\x80' (indicating SSL).
|
||||
|
||||
.IP *
|
||||
Flash security policy: websockify detects flash security policy requests (again by sniffing the first packet) and answers with an appropriate flash security policy response (and then closes the port). This means no separate flash security policy server is needed for supporting the flash WebSockets fallback emulator.
|
||||
|
||||
.IP *
|
||||
Session recording: This feature that allows recording of the traffic sent and received from the client to a file using the --record option.
|
||||
|
||||
.IP *
|
||||
Mini-webserver: websockify can detect and respond to normal web requests on the same port as the WebSockets proxy and Flash security policy. This functionality is activate with the --web DIR option where DIR is the root of the web directory to serve.
|
||||
Mini-webserver: websockify can detect and respond to normal web requests on the same port as the WebSockets proxy. This functionality is activate with the --web DIR option where DIR is the root of the web directory to serve.
|
||||
|
||||
.IP *
|
||||
Wrap a program: see the "Wrap a Program" section below.
|
||||
|
||||
@@ -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 */
|
||||
@@ -362,18 +362,3 @@ if (Util.Engine.webkit) {
|
||||
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};
|
||||
}());
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
* 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.
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,391 +0,0 @@
|
||||
// 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();
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
||||
@@ -3,10 +3,8 @@
|
||||
* 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 is similar to the standard WebSocket object but with extra
|
||||
* buffer handling.
|
||||
*
|
||||
* Websock has built-in receive queue buffering; the message event
|
||||
* does not contain actual data but is simply a notification that
|
||||
@@ -14,411 +12,345 @@
|
||||
* read binary data off of the receive queue.
|
||||
*/
|
||||
|
||||
/*jslint browser: true, bitwise: false, plusplus: false */
|
||||
/*global Util, Base64 */
|
||||
/* [module]
|
||||
* import Util from "./util";
|
||||
*/
|
||||
|
||||
/*jslint browser: true, bitwise: true */
|
||||
/*global Util*/
|
||||
|
||||
// Load Flash WebSocket emulator if needed
|
||||
/* [module] export default */ function Websock() {
|
||||
"use strict";
|
||||
|
||||
// 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;
|
||||
this._websocket = null; // WebSocket object
|
||||
|
||||
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 */
|
||||
this._rQi = 0; // Receive queue index
|
||||
this._rQlen = 0; // Next write position in the receive queue
|
||||
this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
|
||||
this._rQmax = this._rQbufferSize / 8;
|
||||
// called in init: this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._rQ = null; // Receive queue
|
||||
|
||||
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();
|
||||
this._sQbufferSize = 1024 * 10; // 10 KiB
|
||||
// called in init: this._sQ = new Uint8Array(this._sQbufferSize);
|
||||
this._sQlen = 0;
|
||||
this._sQ = null; // Send queue
|
||||
|
||||
this._eventHandlers = {
|
||||
'message': function () {},
|
||||
'open': function () {},
|
||||
'close': function () {},
|
||||
'error': function () {}
|
||||
};
|
||||
};
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
// this has performance issues in some versions Chromium, and
|
||||
// doesn't gain a tremendous amount of performance increase in Firefox
|
||||
// at the moment. It may be valuable to turn it on in the future.
|
||||
var ENABLE_COPYWITHIN = false;
|
||||
|
||||
var MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
|
||||
|
||||
var typedArrayToString = (function () {
|
||||
// This is only for PhantomJS, which doesn't like apply-ing
|
||||
// with Typed Arrays
|
||||
try {
|
||||
var arr = new Uint8Array([1, 2, 3]);
|
||||
String.fromCharCode.apply(null, arr);
|
||||
return function (a) { return String.fromCharCode.apply(null, a); };
|
||||
} catch (ex) {
|
||||
return function (a) {
|
||||
return String.fromCharCode.apply(
|
||||
null, Array.prototype.slice.call(a));
|
||||
};
|
||||
}
|
||||
Util.load_scripts(["web-socket-js/swfobject.js",
|
||||
"web-socket-js/web_socket.js"]);
|
||||
}());
|
||||
}
|
||||
})();
|
||||
|
||||
Websock.prototype = {
|
||||
// Getters and Setters
|
||||
get_sQ: function () {
|
||||
return this._sQ;
|
||||
},
|
||||
|
||||
function Websock() {
|
||||
"use strict";
|
||||
get_rQ: function () {
|
||||
return this._rQ;
|
||||
},
|
||||
|
||||
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
|
||||
get_rQi: function () {
|
||||
return this._rQi;
|
||||
},
|
||||
|
||||
eventHandlers = {
|
||||
'message' : function() {},
|
||||
'open' : function() {},
|
||||
'close' : function() {},
|
||||
'error' : function() {}
|
||||
},
|
||||
set_rQi: function (val) {
|
||||
this._rQi = val;
|
||||
},
|
||||
|
||||
test_mode = false;
|
||||
// Receive Queue
|
||||
rQlen: function () {
|
||||
return this._rQlen - this._rQi;
|
||||
},
|
||||
|
||||
rQpeek8: function () {
|
||||
return this._rQ[this._rQi];
|
||||
},
|
||||
|
||||
//
|
||||
// Queue public functions
|
||||
//
|
||||
rQshift8: function () {
|
||||
return this._rQ[this._rQi++];
|
||||
},
|
||||
|
||||
function get_sQ() {
|
||||
return sQ;
|
||||
}
|
||||
rQskip8: function () {
|
||||
this._rQi++;
|
||||
},
|
||||
|
||||
function get_rQ() {
|
||||
return rQ;
|
||||
}
|
||||
function get_rQi() {
|
||||
return rQi;
|
||||
}
|
||||
function set_rQi(val) {
|
||||
rQi = val;
|
||||
}
|
||||
rQskipBytes: function (num) {
|
||||
this._rQi += num;
|
||||
},
|
||||
|
||||
function rQlen() {
|
||||
return rQ.length - rQi;
|
||||
}
|
||||
// TODO(directxman12): test performance with these vs a DataView
|
||||
rQshift16: function () {
|
||||
return (this._rQ[this._rQi++] << 8) +
|
||||
this._rQ[this._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;
|
||||
}
|
||||
rQshift32: function () {
|
||||
return (this._rQ[this._rQi++] << 24) +
|
||||
(this._rQ[this._rQi++] << 16) +
|
||||
(this._rQ[this._rQi++] << 8) +
|
||||
this._rQ[this._rQi++];
|
||||
},
|
||||
|
||||
}
|
||||
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);
|
||||
}
|
||||
rQshiftStr: function (len) {
|
||||
if (typeof(len) === 'undefined') { len = this.rQlen(); }
|
||||
var arr = new Uint8Array(this._rQ.buffer, this._rQi, len);
|
||||
this._rQi += len;
|
||||
return typedArrayToString(arr);
|
||||
},
|
||||
|
||||
function rQslice(start, end) {
|
||||
if (end) {
|
||||
return rQ.slice(rQi + start, rQi + end);
|
||||
} else {
|
||||
return rQ.slice(rQi + start);
|
||||
}
|
||||
}
|
||||
rQshiftBytes: function (len) {
|
||||
if (typeof(len) === 'undefined') { len = this.rQlen(); }
|
||||
this._rQi += len;
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
|
||||
},
|
||||
|
||||
// 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");
|
||||
rQshiftTo: function (target, len) {
|
||||
if (len === undefined) { len = this.rQlen(); }
|
||||
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ
|
||||
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
|
||||
this._rQi += len;
|
||||
},
|
||||
|
||||
rQwhole: function () {
|
||||
return new Uint8Array(this._rQ.buffer, 0, this._rQlen);
|
||||
},
|
||||
|
||||
rQslice: function (start, end) {
|
||||
if (end) {
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
|
||||
} else {
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start);
|
||||
}
|
||||
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;
|
||||
// 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.
|
||||
rQwait: function (msg, num, goback) {
|
||||
var rQlen = this._rQlen - this._rQi; // Skip rQlen() function call
|
||||
if (rQlen < num) {
|
||||
if (goback) {
|
||||
if (this._rQi < goback) {
|
||||
throw new Error("rQwait cannot backup " + goback + " bytes");
|
||||
}
|
||||
this._rQi -= goback;
|
||||
}
|
||||
return true; // true means need more data
|
||||
}
|
||||
} 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");
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// Send Queue
|
||||
|
||||
// Set event handlers
|
||||
function on(evt, handler) {
|
||||
eventHandlers[evt] = handler;
|
||||
}
|
||||
flush: function () {
|
||||
if (this._websocket.bufferedAmount !== 0) {
|
||||
Util.Debug("bufferedAmount: " + this._websocket.bufferedAmount);
|
||||
}
|
||||
|
||||
function init(protocols, ws_schema) {
|
||||
rQ = [];
|
||||
rQi = 0;
|
||||
sQ = [];
|
||||
websocket = null;
|
||||
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
|
||||
this._websocket.send(this._encode_message());
|
||||
this._sQlen = 0;
|
||||
}
|
||||
},
|
||||
|
||||
var bt = false,
|
||||
wsbt = false,
|
||||
try_binary = false;
|
||||
send: function (arr) {
|
||||
this._sQ.set(arr, this._sQlen);
|
||||
this._sQlen += arr.length;
|
||||
this.flush();
|
||||
},
|
||||
|
||||
// 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
|
||||
}
|
||||
send_string: function (str) {
|
||||
this.send(str.split('').map(function (chr) {
|
||||
return chr.charCodeAt(0);
|
||||
}));
|
||||
},
|
||||
|
||||
// Default protocols if not specified
|
||||
if (typeof(protocols) === "undefined") {
|
||||
if (wsbt) {
|
||||
protocols = ['binary', 'base64'];
|
||||
} else {
|
||||
protocols = 'base64';
|
||||
}
|
||||
}
|
||||
// Event Handlers
|
||||
off: function (evt) {
|
||||
this._eventHandlers[evt] = function () {};
|
||||
},
|
||||
|
||||
// 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");
|
||||
on: function (evt, handler) {
|
||||
this._eventHandlers[evt] = handler;
|
||||
},
|
||||
|
||||
_allocate_buffers: function () {
|
||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._sQ = new Uint8Array(this._sQbufferSize);
|
||||
},
|
||||
|
||||
init: function () {
|
||||
this._allocate_buffers();
|
||||
this._rQi = 0;
|
||||
this._websocket = null;
|
||||
},
|
||||
|
||||
open: function (uri, protocols) {
|
||||
var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
|
||||
this.init();
|
||||
|
||||
this._websocket = new WebSocket(uri, protocols);
|
||||
this._websocket.binaryType = 'arraybuffer';
|
||||
|
||||
this._websocket.onmessage = this._recv_message.bind(this);
|
||||
this._websocket.onopen = (function () {
|
||||
Util.Debug('>> WebSock.onopen');
|
||||
if (this._websocket.protocol) {
|
||||
Util.Info("Server choose sub-protocol: " + this._websocket.protocol);
|
||||
}
|
||||
|
||||
this._eventHandlers.open();
|
||||
Util.Debug("<< WebSock.onopen");
|
||||
}).bind(this);
|
||||
this._websocket.onclose = (function (e) {
|
||||
Util.Debug(">> WebSock.onclose");
|
||||
this._eventHandlers.close(e);
|
||||
Util.Debug("<< WebSock.onclose");
|
||||
}).bind(this);
|
||||
this._websocket.onerror = (function (e) {
|
||||
Util.Debug(">> WebSock.onerror: " + e);
|
||||
this._eventHandlers.error(e);
|
||||
Util.Debug("<< WebSock.onerror: " + e);
|
||||
}).bind(this);
|
||||
},
|
||||
|
||||
close: function () {
|
||||
if (this._websocket) {
|
||||
if ((this._websocket.readyState === WebSocket.OPEN) ||
|
||||
(this._websocket.readyState === WebSocket.CONNECTING)) {
|
||||
Util.Info("Closing WebSocket connection");
|
||||
this._websocket.close();
|
||||
}
|
||||
|
||||
this._websocket.onmessage = function (e) { return; };
|
||||
}
|
||||
},
|
||||
|
||||
// private methods
|
||||
_encode_message: function () {
|
||||
// Put in a binary arraybuffer
|
||||
// according to the spec, you can send ArrayBufferViews with the send method
|
||||
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
|
||||
},
|
||||
|
||||
_expand_compact_rQ: function (min_fit) {
|
||||
var resizeNeeded = min_fit || this._rQlen - this._rQi > this._rQbufferSize / 2;
|
||||
if (resizeNeeded) {
|
||||
if (!min_fit) {
|
||||
// just double the size if we need to do compaction
|
||||
this._rQbufferSize *= 2;
|
||||
} else {
|
||||
new_protocols.push(protocols[i]);
|
||||
// otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8
|
||||
this._rQbufferSize = (this._rQlen - this._rQi + min_fit) * 8;
|
||||
}
|
||||
}
|
||||
if (new_protocols.length > 0) {
|
||||
protocols = new_protocols;
|
||||
|
||||
// we don't want to grow unboundedly
|
||||
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
|
||||
this._rQbufferSize = MAX_RQ_GROW_SIZE;
|
||||
if (this._rQbufferSize - this._rQlen - this._rQi < min_fit) {
|
||||
throw new Exception("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
|
||||
}
|
||||
}
|
||||
|
||||
if (resizeNeeded) {
|
||||
var old_rQbuffer = this._rQ.buffer;
|
||||
this._rQmax = this._rQbufferSize / 8;
|
||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi));
|
||||
} else {
|
||||
throw("Only WebSocket binary sub-protocol was requested and not supported.");
|
||||
if (ENABLE_COPYWITHIN) {
|
||||
this._rQ.copyWithin(0, this._rQi);
|
||||
} else {
|
||||
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi));
|
||||
}
|
||||
}
|
||||
|
||||
this._rQlen = this._rQlen - this._rQi;
|
||||
this._rQi = 0;
|
||||
},
|
||||
|
||||
_decode_message: function (data) {
|
||||
// push arraybuffer values onto the end
|
||||
var u8 = new Uint8Array(data);
|
||||
if (u8.length > this._rQbufferSize - this._rQlen) {
|
||||
this._expand_compact_rQ(u8.length);
|
||||
}
|
||||
this._rQ.set(u8, this._rQlen);
|
||||
this._rQlen += u8.length;
|
||||
},
|
||||
|
||||
_recv_message: function (e) {
|
||||
try {
|
||||
this._decode_message(e.data);
|
||||
if (this.rQlen() > 0) {
|
||||
this._eventHandlers.message();
|
||||
// Compact the receive queue
|
||||
if (this._rQlen == this._rQi) {
|
||||
this._rQlen = 0;
|
||||
this._rQi = 0;
|
||||
} else if (this._rQlen > this._rQmax) {
|
||||
this._expand_compact_rQ();
|
||||
}
|
||||
} else {
|
||||
Util.Debug("Ignoring empty message");
|
||||
}
|
||||
} catch (exc) {
|
||||
var exception_str = "";
|
||||
if (exc.name) {
|
||||
exception_str += "\n name: " + exc.name + "\n";
|
||||
exception_str += " message: " + exc.message + "\n";
|
||||
}
|
||||
|
||||
if (typeof exc.description !== 'undefined') {
|
||||
exception_str += " description: " + exc.description + "\n";
|
||||
}
|
||||
|
||||
if (typeof exc.stack !== 'undefined') {
|
||||
exception_str += exc.stack;
|
||||
}
|
||||
|
||||
if (exception_str.length > 0) {
|
||||
Util.Error("recv_message, caught exception: " + exception_str);
|
||||
} else {
|
||||
Util.Error("recv_message, caught exception: " + exc);
|
||||
}
|
||||
|
||||
if (typeof exc.name !== 'undefined') {
|
||||
this._eventHandlers.error(exc.name + ": " + exc.message);
|
||||
} else {
|
||||
this._eventHandlers.error(exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"author": "Joel Martin <github@martintribe.org> (http://github.com/kanaka)",
|
||||
"name": "websockify",
|
||||
"description": "websockify is a WebSocket-to-TCP proxy/bridge",
|
||||
"license": "LGPL-3",
|
||||
"version": "0.6.0",
|
||||
"license": "LGPL-3.0",
|
||||
"version": "0.8.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/kanaka/websockify.git"
|
||||
@@ -17,7 +17,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": ">=0.4.27",
|
||||
"base64": "latest",
|
||||
"optimist": "latest",
|
||||
"policyfile": "latest"
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
// Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
|
||||
// Known to work with node 0.8.9
|
||||
// Requires node modules: ws, optimist and policyfile
|
||||
// npm install ws optimist policyfile
|
||||
// Requires node modules: ws and optimist
|
||||
// npm install ws optimist
|
||||
|
||||
|
||||
var argv = require('optimist').argv,
|
||||
@@ -16,7 +16,6 @@ var argv = require('optimist').argv,
|
||||
url = require('url'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
policyfile = require('policyfile'),
|
||||
|
||||
Buffer = require('buffer').Buffer,
|
||||
WebSocketServer = require('ws').Server,
|
||||
@@ -42,11 +41,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 +59,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 +114,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,10 +162,6 @@ 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);
|
||||
});
|
||||
|
||||
// Attach Flash policyfile answer service
|
||||
policyfile.createServer().listen(-1, webServer);
|
||||
|
||||
@@ -578,12 +578,6 @@ ws_ctx_t *do_handshake(int sock) {
|
||||
if (len == 0) {
|
||||
handler_msg("ignoring empty handshake\n");
|
||||
return NULL;
|
||||
} else if (bcmp(handshake, "<policy-file-request/>", 22) == 0) {
|
||||
len = recv(sock, handshake, 1024, 0);
|
||||
handshake[len] = 0;
|
||||
handler_msg("sending flash policy response\n");
|
||||
send(sock, POLICY_RESPONSE, sizeof(POLICY_RESPONSE), 0);
|
||||
return NULL;
|
||||
} else if ((bcmp(handshake, "\x16", 1) == 0) ||
|
||||
(bcmp(handshake, "\x80", 1) == 0)) {
|
||||
// SSL
|
||||
@@ -612,15 +606,28 @@ ws_ctx_t *do_handshake(int sock) {
|
||||
}
|
||||
offset = 0;
|
||||
for (i = 0; i < 10; i++) {
|
||||
len = ws_recv(ws_ctx, handshake+offset, 4096);
|
||||
if (len == 0) {
|
||||
/* (offset + 1): reserve one byte for the trailing '\0' */
|
||||
if (0 > (len = ws_recv(ws_ctx, handshake + offset, sizeof(handshake) - (offset + 1)))) {
|
||||
handler_emsg("Read error during handshake: %m\n");
|
||||
free_ws_ctx(ws_ctx);
|
||||
return NULL;
|
||||
} else if (0 == len) {
|
||||
handler_emsg("Client closed during handshake\n");
|
||||
free_ws_ctx(ws_ctx);
|
||||
return NULL;
|
||||
}
|
||||
offset += len;
|
||||
handshake[offset] = 0;
|
||||
if (strstr(handshake, "\r\n\r\n")) {
|
||||
break;
|
||||
} else if (sizeof(handshake) <= (size_t)(offset + 1)) {
|
||||
handler_emsg("Oversized handshake\n");
|
||||
free_ws_ctx(ws_ctx);
|
||||
return NULL;
|
||||
} else if (9 == i) {
|
||||
handler_emsg("Incomplete handshake\n");
|
||||
free_ws_ctx(ws_ctx);
|
||||
return NULL;
|
||||
}
|
||||
usleep(10);
|
||||
}
|
||||
@@ -628,6 +635,7 @@ ws_ctx_t *do_handshake(int sock) {
|
||||
//handler_msg("handshake: %s\n", handshake);
|
||||
if (!parse_handshake(ws_ctx, handshake)) {
|
||||
handler_emsg("Invalid WS request\n");
|
||||
free_ws_ctx(ws_ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
import websockify
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
version = '0.6.0'
|
||||
version = '0.8.0'
|
||||
name = 'websockify'
|
||||
long_description = open("README.md").read() + "\n" + \
|
||||
open("CHANGES.txt").read() + "\n"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
mox3
|
||||
nose
|
||||
@@ -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))
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Native Base64 Tests</title>
|
||||
<script src="include/util.js"></script>
|
||||
<script src="include/webutil.js"></script>
|
||||
<script src="include/base64.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Native Base64 Tests</h1>
|
||||
|
||||
<br>
|
||||
Messages:<br>
|
||||
<textarea id="debug" style="font-size: 9px;" cols=80 rows=25></textarea>
|
||||
|
||||
<br>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
function debug(str) {
|
||||
console.log(str);
|
||||
cell = $D('debug');
|
||||
cell.innerHTML += str + "\n";
|
||||
cell.scrollTop = cell.scrollHeight;
|
||||
}
|
||||
|
||||
function assertRun(code, result) {
|
||||
try {
|
||||
var actual = eval(code);
|
||||
} catch (exc) {
|
||||
debug("FAIL: '" + code + "' threw an exception");
|
||||
fail += 1;
|
||||
return false;
|
||||
}
|
||||
if (actual !== result) {
|
||||
debug("FAIL: '" + code + "' returned '" + actual + "', expected '" + result + "'");
|
||||
fail += 1;
|
||||
return false;
|
||||
}
|
||||
debug("PASS: '" + code + "' returned expected '" + result +"'");
|
||||
pass += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
function Base64_decode(data) {
|
||||
var arr = Base64.decode (data);
|
||||
return arr.map(function (num) {
|
||||
return String.fromCharCode(num); } ).join('');
|
||||
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
var str;
|
||||
debug('onload');
|
||||
fail = 0;
|
||||
pass = 0;
|
||||
assertRun('window.btoa("hello world")', 'aGVsbG8gd29ybGQ=');
|
||||
assertRun('window.btoa("a")', 'YQ==');
|
||||
assertRun('window.btoa("ab")', 'YWI=');
|
||||
assertRun('window.btoa("abc")', 'YWJj');
|
||||
assertRun('window.btoa("abcd")', 'YWJjZA==');
|
||||
assertRun('window.btoa("abcde")', 'YWJjZGU=');
|
||||
assertRun('window.btoa("abcdef")', 'YWJjZGVm');
|
||||
assertRun('window.btoa("abcdefg")', 'YWJjZGVmZw==');
|
||||
assertRun('window.btoa("abcdefgh")', 'YWJjZGVmZ2g=');
|
||||
|
||||
assertRun('window.atob("aGVsbG8gd29ybGQ=")', 'hello world');
|
||||
assertRun('Base64_decode("aGVsbG8gd29ybGQ=")', 'hello world');
|
||||
assertRun('window.atob("YQ==")', 'a');
|
||||
assertRun('Base64_decode("YQ==")', 'a');
|
||||
assertRun('window.atob("YWI=")', 'ab');
|
||||
assertRun('Base64_decode("YWI=")', 'ab');
|
||||
assertRun('window.atob("YWJj")', 'abc');
|
||||
assertRun('Base64_decode("YWJj")', 'abc');
|
||||
assertRun('window.atob("YWJjZA==")', 'abcd');
|
||||
assertRun('Base64_decode("YWJjZA==")', 'abcd');
|
||||
assertRun('window.atob("YWJjZGU=")', 'abcde');
|
||||
assertRun('Base64_decode("YWJjZGU=")', 'abcde');
|
||||
assertRun('window.atob("YWJjZGVm")', 'abcdef');
|
||||
assertRun('Base64_decode("YWJjZGVm")', 'abcdef');
|
||||
|
||||
assertRun('typeof window.btoa', 'function');
|
||||
assertRun('window.btoa("")', '');
|
||||
assertRun('window.btoa(null)', '');
|
||||
assertRun('window.atob(window.btoa(window))', window.toString()); // "[object DOMWindow]"
|
||||
assertRun('window.btoa("\\u0080\\u0081")', 'gIE=');
|
||||
|
||||
debug("Tests failed: " + fail);
|
||||
debug("Tests passed: " + pass);
|
||||
}
|
||||
</script>
|
||||
@@ -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));
|
||||
@@ -4,7 +4,6 @@
|
||||
<title>WebSockets Echo Test</title>
|
||||
<script src="include/util.js"></script>
|
||||
<script src="include/webutil.js"></script>
|
||||
<script src="include/base64.js"></script>
|
||||
<script src="include/websock.js"></script>
|
||||
<!-- Uncomment to activate firebug lite -->
|
||||
<!--
|
||||
|
||||
@@ -12,9 +12,9 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||
|
||||
import os, sys, select, optparse, logging
|
||||
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
||||
from websockify.websocket import WebSocketServer, WebSocketRequestHandler
|
||||
from websockify.websockifyserver import WebSockifyServer, WebSockifyRequestHandler
|
||||
|
||||
class WebSocketEcho(WebSocketRequestHandler):
|
||||
class WebSocketEcho(WebSockifyRequestHandler):
|
||||
"""
|
||||
WebSockets server that echos back whatever is received from the
|
||||
client. """
|
||||
@@ -48,7 +48,7 @@ class WebSocketEcho(WebSocketRequestHandler):
|
||||
cqueue.extend(frames)
|
||||
|
||||
if closed:
|
||||
self.send_close()
|
||||
break
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = optparse.OptionParser(usage="%prog [options] listen_port")
|
||||
@@ -71,6 +71,6 @@ if __name__ == '__main__':
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
opts.web = "."
|
||||
server = WebSocketServer(WebSocketEcho, **opts.__dict__)
|
||||
server = WebSockifyServer(WebSocketEcho, **opts.__dict__)
|
||||
server.start_server()
|
||||
|
||||
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import sys
|
||||
import optparse
|
||||
import select
|
||||
|
||||
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
||||
from websockify.websocket import WebSocket, \
|
||||
WebSocketWantReadError, WebSocketWantWriteError
|
||||
|
||||
parser = optparse.OptionParser(usage="%prog URL")
|
||||
(opts, args) = parser.parse_args()
|
||||
|
||||
try:
|
||||
if len(args) != 1: raise
|
||||
URL = args[0]
|
||||
except:
|
||||
parser.error("Invalid arguments")
|
||||
|
||||
sock = WebSocket()
|
||||
print("Connecting to %s..." % URL)
|
||||
sock.connect(URL)
|
||||
print("Connected.")
|
||||
|
||||
def send(msg):
|
||||
while True:
|
||||
try:
|
||||
sock.sendmsg(msg)
|
||||
break
|
||||
except WebSocketWantReadError:
|
||||
msg = ''
|
||||
ins, outs, excepts = select.select([sock], [], [])
|
||||
if excepts: raise Exception("Socket exception")
|
||||
except WebSocketWantWriteError:
|
||||
msg = ''
|
||||
ins, outs, excepts = select.select([], [sock], [])
|
||||
if excepts: raise Exception("Socket exception")
|
||||
|
||||
def read():
|
||||
while True:
|
||||
try:
|
||||
return sock.recvmsg()
|
||||
except WebSocketWantReadError:
|
||||
ins, outs, excepts = select.select([sock], [], [])
|
||||
if excepts: raise Exception("Socket exception")
|
||||
except WebSocketWantWriteError:
|
||||
ins, outs, excepts = select.select([], [sock], [])
|
||||
if excepts: raise Exception("Socket exception")
|
||||
|
||||
counter = 1
|
||||
while True:
|
||||
msg = "Message #%d" % counter
|
||||
counter += 1
|
||||
send(msg)
|
||||
print("Sent message: %r" % msg)
|
||||
|
||||
while True:
|
||||
ins, outs, excepts = select.select([sock], [], [], 1.0)
|
||||
if excepts: raise Exception("Socket exception")
|
||||
|
||||
if ins == []:
|
||||
break
|
||||
|
||||
while True:
|
||||
msg = read()
|
||||
print("Received message: %r" % msg)
|
||||
|
||||
if not sock.pending():
|
||||
break
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
<head>
|
||||
<title>WebSockets Latency Test</title>
|
||||
<script src="include/base64.js"></script>
|
||||
<script src="include/util.js"></script>
|
||||
<script src="include/webutil.js"></script>
|
||||
<script src="include/websock.js"></script>
|
||||
@@ -57,7 +56,7 @@
|
||||
|
||||
<script>
|
||||
|
||||
var host = null, port = null, sendDelay = 0, actualSendDelay,
|
||||
var host = null, port = null, sendDelay = 0,
|
||||
ws = null, send_ref = null,
|
||||
sent, received, latencies, ltotal, laverage, lrunning, lmin, lmax,
|
||||
run_length = 40,
|
||||
@@ -92,8 +91,8 @@
|
||||
now = (new Date()).getTime(); // Early as possible
|
||||
|
||||
arr = ws.rQshiftBytes(ws.rQlen());
|
||||
first = String.fromCharCode(arr.shift());
|
||||
last = String.fromCharCode(arr.pop());
|
||||
first = String.fromCharCode(arr[0]);
|
||||
last = String.fromCharCode(arr[arr.length-1]);
|
||||
|
||||
if (first != "^") {
|
||||
message("Error: packet missing start char '^'");
|
||||
@@ -105,9 +104,11 @@
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
arr = arr.map(function(num) {
|
||||
return String.fromCharCode(num);
|
||||
} ).join('').split(':');
|
||||
text = ''
|
||||
for (var i = 1; i < arr.length-1; i++) {
|
||||
text += String.fromCharCode(arr[i]);
|
||||
}
|
||||
arr = text.split(':');
|
||||
seq = arr[0];
|
||||
timestamp = parseInt(arr[1],10);
|
||||
rpayload = arr[2];
|
||||
@@ -152,19 +153,6 @@
|
||||
|
||||
function sendMsg() {
|
||||
var arr = [];
|
||||
if (! ws.flush() ) {
|
||||
message("WebSocket not ready, backing off");
|
||||
actualSendDelay = actualSendDelay * 2;
|
||||
send_ref = setTimeout(sendMsg, actualSendDelay);
|
||||
return false;
|
||||
} else {
|
||||
// Scale the delay down to the requested minimum
|
||||
if (actualSendDelay > sendDelay) {
|
||||
message("WebSocket ready, increasing presure");
|
||||
actualSendDelay = Math.max(actualSendDelay / 2, sendDelay);
|
||||
}
|
||||
}
|
||||
|
||||
timestamp = (new Date()).getTime();
|
||||
arr.pushStr("^" + send_seq + ":" + timestamp + ":" + payload + "$");
|
||||
send_seq ++;
|
||||
@@ -172,7 +160,7 @@
|
||||
sent++;
|
||||
|
||||
showStats();
|
||||
send_ref = setTimeout(sendMsg, actualSendDelay);
|
||||
send_ref = setTimeout(sendMsg, sendDelay);
|
||||
}
|
||||
|
||||
function showStats() {
|
||||
@@ -246,7 +234,6 @@
|
||||
lrunning = 0;
|
||||
lmin = 999999999;
|
||||
lmax = 0;
|
||||
actualSendDelay = sendDelay;
|
||||
|
||||
$D('connectButton').value = "Stop";
|
||||
$D('connectButton').onclick = disconnect;
|
||||
@@ -275,11 +262,6 @@
|
||||
|
||||
window.onload = function() {
|
||||
console.log("onload");
|
||||
if (Websock_native) {
|
||||
message("Using native WebSockets");
|
||||
} else {
|
||||
message("initializing web-socket-js flash bridge");
|
||||
}
|
||||
var url = document.location.href;
|
||||
$D('host').value = (url.match(/host=([^&#]*)/) || ['',window.location.hostname])[1];
|
||||
$D('port').value = (url.match(/port=([^&#]*)/) || ['',window.location.port])[1];
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
<title>WebSockets Load Test</title>
|
||||
<script src="include/util.js"></script>
|
||||
<script src="include/webutil.js"></script>
|
||||
<script src="include/base64.js"></script>
|
||||
<script src="include/websock.js"></script>
|
||||
<!-- Uncomment to activate firebug lite -->
|
||||
<!--
|
||||
@@ -221,26 +220,8 @@
|
||||
console.log("<< disconnect");
|
||||
}
|
||||
|
||||
|
||||
/* If no builtin websockets then load web_socket.js */
|
||||
if (window.WebSocket) {
|
||||
VNC_native_ws = true;
|
||||
} else {
|
||||
VNC_native_ws = false;
|
||||
console.log("Loading web-socket-js flash bridge");
|
||||
var extra = "<script src='include/web-socket-js/swfobject.js'><\/script>";
|
||||
extra += "<script src='include/web-socket-js/FABridge.js'><\/script>";
|
||||
extra += "<script src='include/web-socket-js/web_socket.js'><\/script>";
|
||||
document.write(extra);
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
console.log("onload");
|
||||
if (!VNC_native_ws) {
|
||||
console.log("initializing web-socket-js flash bridge");
|
||||
WebSocket.__swfLocation = "include/web-socket-js/WebSocketMain.swf";
|
||||
WebSocket.__initialize();
|
||||
}
|
||||
var url = document.location.href;
|
||||
$D('host').value = (url.match(/host=([^&#]*)/) || ['',''])[1];
|
||||
$D('port').value = (url.match(/port=([^&#]*)/) || ['',''])[1];
|
||||
|
||||
@@ -8,9 +8,9 @@ given a sequence number. Any errors are reported and counted.
|
||||
|
||||
import sys, os, select, random, time, optparse, logging
|
||||
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
||||
from websockify.websocket import WebSocketServer, WebSocketRequestHandler
|
||||
from websockify.websockifyserver import WebSockifyServer, WebSockifyRequestHandler
|
||||
|
||||
class WebSocketLoadServer(WebSocketServer):
|
||||
class WebSocketLoadServer(WebSockifyServer):
|
||||
|
||||
recv_cnt = 0
|
||||
send_cnt = 0
|
||||
@@ -18,10 +18,10 @@ class WebSocketLoadServer(WebSocketServer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.delay = kwargs.pop('delay')
|
||||
|
||||
WebSocketServer.__init__(self, *args, **kwargs)
|
||||
WebSockifyServer.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class WebSocketLoad(WebSocketRequestHandler):
|
||||
class WebSocketLoad(WebSockifyRequestHandler):
|
||||
|
||||
max_packet_size = 10000
|
||||
|
||||
@@ -35,12 +35,10 @@ class WebSocketLoad(WebSocketRequestHandler):
|
||||
self.send_cnt = 0
|
||||
self.recv_cnt = 0
|
||||
|
||||
try:
|
||||
self.responder(self.request)
|
||||
except:
|
||||
print "accumulated errors:", self.errors
|
||||
self.errors = 0
|
||||
raise
|
||||
self.responder(self.request)
|
||||
|
||||
print "accumulated errors:", self.errors
|
||||
self.errors = 0
|
||||
|
||||
def responder(self, client):
|
||||
c_pend = 0
|
||||
@@ -62,7 +60,7 @@ class WebSocketLoad(WebSocketRequestHandler):
|
||||
print err
|
||||
|
||||
if closed:
|
||||
self.send_close()
|
||||
break
|
||||
|
||||
now = time.time() * 1000
|
||||
if client in outs:
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
}
|
||||
uri = scheme + host + ":" + port;
|
||||
message("connecting to " + uri);
|
||||
ws = new WebSocket(uri, "base64");
|
||||
ws = new WebSocket(uri);
|
||||
|
||||
ws.onmessage = function(e) {
|
||||
//console.log(">> WebSockets.onmessage");
|
||||
@@ -139,26 +139,8 @@
|
||||
console.log("<< disconnect");
|
||||
}
|
||||
|
||||
|
||||
/* If no builtin websockets then load web_socket.js */
|
||||
if (window.WebSocket) {
|
||||
VNC_native_ws = true;
|
||||
} else {
|
||||
VNC_native_ws = false;
|
||||
console.log("Loading web-socket-js flash bridge");
|
||||
var extra = "<script src='include/web-socket-js/swfobject.js'><\/script>";
|
||||
extra += "<script src='include/web-socket-js/FABridge.js'><\/script>";
|
||||
extra += "<script src='include/web-socket-js/web_socket.js'><\/script>";
|
||||
document.write(extra);
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
console.log("onload");
|
||||
if (!VNC_native_ws) {
|
||||
console.log("initializing web-socket-js flash bridge");
|
||||
WebSocket.__swfLocation = "include/web-socket-js/WebSocketMain.swf";
|
||||
WebSocket.__initialize();
|
||||
}
|
||||
var url = document.location.href;
|
||||
$D('host').value = (url.match(/host=([^&#]*)/) || ['',''])[1];
|
||||
$D('port').value = (url.match(/port=([^&#]*)/) || ['',''])[1];
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<head>
|
||||
<title>Websock Simple Client</title>
|
||||
<script src="include/util.js"></script>
|
||||
<script src="include/base64.js"></script>
|
||||
<script src="include/websock.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
""" Unit tests for Authentication plugins"""
|
||||
|
||||
from websockify.auth_plugins import BasicHTTPAuth, AuthenticationError
|
||||
import unittest
|
||||
|
||||
|
||||
class BasicHTTPAuthTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.plugin = BasicHTTPAuth('Aladdin:open sesame')
|
||||
|
||||
def test_no_auth(self):
|
||||
headers = {}
|
||||
self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234')
|
||||
|
||||
def test_invalid_password(self):
|
||||
headers = {'Authorization': 'Basic QWxhZGRpbjpzZXNhbWUgc3RyZWV0'}
|
||||
self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234')
|
||||
|
||||
def test_valid_password(self):
|
||||
headers = {'Authorization': 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='}
|
||||
self.plugin.authenticate(headers, 'localhost', '1234')
|
||||
|
||||
def test_garbage_auth(self):
|
||||
headers = {'Authorization': 'Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxx'}
|
||||
self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234')
|
||||
@@ -15,229 +15,172 @@
|
||||
# under the License.
|
||||
|
||||
""" Unit tests for websocket """
|
||||
import errno
|
||||
import os
|
||||
import logging
|
||||
import select
|
||||
import shutil
|
||||
import socket
|
||||
import ssl
|
||||
import stubout
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from ssl import SSLError
|
||||
from websockify import websocket as websocket
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
from websockify import websocket
|
||||
|
||||
class FakeSocket:
|
||||
def __init__(self):
|
||||
self.data = b''
|
||||
|
||||
class MockConnection(object):
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
def send(self, buf):
|
||||
self.data += buf
|
||||
return len(buf)
|
||||
|
||||
def makefile(self, mode='r', bufsize=-1):
|
||||
return open(self.path, mode, bufsize)
|
||||
class AcceptTestCase(unittest.TestCase):
|
||||
def test_success(self):
|
||||
ws = websocket.WebSocket()
|
||||
sock = FakeSocket()
|
||||
ws.accept(sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '13',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='})
|
||||
self.assertEqual(sock.data[:13], b'HTTP/1.1 101 ')
|
||||
self.assertTrue(b'\r\nUpgrade: websocket\r\n' in sock.data)
|
||||
self.assertTrue(b'\r\nConnection: Upgrade\r\n' in sock.data)
|
||||
self.assertTrue(b'\r\nSec-WebSocket-Accept: pczpYSQsvE1vBpTQYjFQPcuoj6M=\r\n' in sock.data)
|
||||
|
||||
def test_bad_version(self):
|
||||
ws = websocket.WebSocket()
|
||||
sock = FakeSocket()
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='})
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '5',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='})
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '20',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='})
|
||||
|
||||
class WebSocketTestCase(unittest.TestCase):
|
||||
def test_bad_upgrade(self):
|
||||
ws = websocket.WebSocket()
|
||||
sock = FakeSocket()
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'Sec-WebSocket-Version': '13',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='})
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'upgrade': 'websocket2',
|
||||
'Sec-WebSocket-Version': '13',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='})
|
||||
|
||||
def _init_logger(self, tmpdir):
|
||||
name = 'websocket-unittest'
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.propagate = True
|
||||
filename = "%s.log" % (name)
|
||||
handler = logging.FileHandler(filename)
|
||||
handler.setFormatter(logging.Formatter("%(message)s"))
|
||||
logger.addHandler(handler)
|
||||
def test_missing_key(self):
|
||||
ws = websocket.WebSocket()
|
||||
sock = FakeSocket()
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '13'})
|
||||
|
||||
def setUp(self):
|
||||
"""Called automatically before each test."""
|
||||
super(WebSocketTestCase, self).setUp()
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
# Temporary dir for test data
|
||||
self.tmpdir = tempfile.mkdtemp()
|
||||
# Put log somewhere persistent
|
||||
self._init_logger('./')
|
||||
# Mock this out cause it screws tests up
|
||||
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
|
||||
self.server = self._get_websockserver(daemon=True,
|
||||
ssl_only=False)
|
||||
self.soc = self.server.socket('localhost')
|
||||
def test_protocol(self):
|
||||
class ProtoSocket(websocket.WebSocket):
|
||||
def select_subprotocol(self, protocol):
|
||||
return 'gazonk'
|
||||
|
||||
def tearDown(self):
|
||||
"""Called automatically after each test."""
|
||||
self.stubs.UnsetAll()
|
||||
shutil.rmtree(self.tmpdir)
|
||||
super(WebSocketTestCase, self).tearDown()
|
||||
ws = ProtoSocket()
|
||||
sock = FakeSocket()
|
||||
ws.accept(sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '13',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q==',
|
||||
'Sec-WebSocket-Protocol': 'foobar gazonk'})
|
||||
self.assertEqual(sock.data[:13], b'HTTP/1.1 101 ')
|
||||
self.assertTrue(b'\r\nSec-WebSocket-Protocol: gazonk\r\n' in sock.data)
|
||||
|
||||
def _get_websockserver(self, **kwargs):
|
||||
return websocket.WebSocketServer(listen_host='localhost',
|
||||
listen_port=80,
|
||||
key=self.tmpdir,
|
||||
web=self.tmpdir,
|
||||
record=self.tmpdir,
|
||||
**kwargs)
|
||||
def test_no_protocol(self):
|
||||
ws = websocket.WebSocket()
|
||||
sock = FakeSocket()
|
||||
ws.accept(sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '13',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='})
|
||||
self.assertEqual(sock.data[:13], b'HTTP/1.1 101 ')
|
||||
self.assertFalse(b'\r\nSec-WebSocket-Protocol:' in sock.data)
|
||||
|
||||
def _mock_os_open_oserror(self, file, flags):
|
||||
raise OSError('')
|
||||
def test_missing_protocol(self):
|
||||
ws = websocket.WebSocket()
|
||||
sock = FakeSocket()
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '13',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q==',
|
||||
'Sec-WebSocket-Protocol': 'foobar gazonk'})
|
||||
|
||||
def _mock_os_close_oserror(self, fd):
|
||||
raise OSError('')
|
||||
def test_protocol(self):
|
||||
class ProtoSocket(websocket.WebSocket):
|
||||
def select_subprotocol(self, protocol):
|
||||
return 'oddball'
|
||||
|
||||
def _mock_os_close_oserror_EBADF(self, fd):
|
||||
raise OSError(errno.EBADF, '')
|
||||
ws = ProtoSocket()
|
||||
sock = FakeSocket()
|
||||
self.assertRaises(Exception, ws.accept,
|
||||
sock, {'upgrade': 'websocket',
|
||||
'Sec-WebSocket-Version': '13',
|
||||
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q==',
|
||||
'Sec-WebSocket-Protocol': 'foobar gazonk'})
|
||||
|
||||
def _mock_socket(self, *args, **kwargs):
|
||||
return self.soc
|
||||
class HyBiEncodeDecodeTestCase(unittest.TestCase):
|
||||
def test_decode_hybi_text(self):
|
||||
buf = b'\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58'
|
||||
ws = websocket.WebSocket()
|
||||
res = ws._decode_hybi(buf)
|
||||
|
||||
def _mock_select(self, rlist, wlist, xlist, timeout=None):
|
||||
return '_mock_select'
|
||||
self.assertEqual(res['fin'], 1)
|
||||
self.assertEqual(res['opcode'], 0x1)
|
||||
self.assertEqual(res['masked'], True)
|
||||
self.assertEqual(res['length'], len(buf))
|
||||
self.assertEqual(res['payload'], b'Hello')
|
||||
|
||||
def _mock_select_exception(self, rlist, wlist, xlist, timeout=None):
|
||||
raise Exception
|
||||
def test_decode_hybi_binary(self):
|
||||
buf = b'\x82\x04\x01\x02\x03\x04'
|
||||
ws = websocket.WebSocket()
|
||||
res = ws._decode_hybi(buf)
|
||||
|
||||
def _mock_select_keyboardinterrupt(self, rlist, wlist,
|
||||
xlist, timeout=None):
|
||||
raise KeyboardInterrupt
|
||||
self.assertEqual(res['fin'], 1)
|
||||
self.assertEqual(res['opcode'], 0x2)
|
||||
self.assertEqual(res['length'], len(buf))
|
||||
self.assertEqual(res['payload'], b'\x01\x02\x03\x04')
|
||||
|
||||
def _mock_select_systemexit(self, rlist, wlist, xlist, timeout=None):
|
||||
sys.exit()
|
||||
def test_decode_hybi_extended_16bit_binary(self):
|
||||
data = (b'\x01\x02\x03\x04' * 65) # len > 126 -- len == 260
|
||||
buf = b'\x82\x7e\x01\x04' + data
|
||||
ws = websocket.WebSocket()
|
||||
res = ws._decode_hybi(buf)
|
||||
|
||||
def test_daemonize_error(self):
|
||||
soc = self._get_websockserver(daemon=True, ssl_only=1, idle_timeout=1)
|
||||
self.stubs.Set(os, 'fork', lambda *args: None)
|
||||
self.stubs.Set(os, 'setsid', lambda *args: None)
|
||||
self.stubs.Set(os, 'close', self._mock_os_close_oserror)
|
||||
self.assertRaises(OSError, soc.daemonize, keepfd=None, chdir='./')
|
||||
self.assertEqual(res['fin'], 1)
|
||||
self.assertEqual(res['opcode'], 0x2)
|
||||
self.assertEqual(res['length'], len(buf))
|
||||
self.assertEqual(res['payload'], data)
|
||||
|
||||
def test_daemonize_EBADF_error(self):
|
||||
soc = self._get_websockserver(daemon=True, ssl_only=1, idle_timeout=1)
|
||||
self.stubs.Set(os, 'fork', lambda *args: None)
|
||||
self.stubs.Set(os, 'setsid', lambda *args: None)
|
||||
self.stubs.Set(os, 'close', self._mock_os_close_oserror_EBADF)
|
||||
self.stubs.Set(os, 'open', self._mock_os_open_oserror)
|
||||
self.assertRaises(OSError, soc.daemonize, keepfd=None, chdir='./')
|
||||
def test_decode_hybi_extended_64bit_binary(self):
|
||||
data = (b'\x01\x02\x03\x04' * 65) # len > 126 -- len == 260
|
||||
buf = b'\x82\x7f\x00\x00\x00\x00\x00\x00\x01\x04' + data
|
||||
ws = websocket.WebSocket()
|
||||
res = ws._decode_hybi(buf)
|
||||
|
||||
def test_decode_hybi(self):
|
||||
soc = self._get_websockserver(daemon=False, ssl_only=1, idle_timeout=1)
|
||||
self.assertRaises(Exception, soc.decode_hybi, 'a' * 128,
|
||||
base64=True)
|
||||
self.assertEqual(res['fin'], 1)
|
||||
self.assertEqual(res['opcode'], 0x2)
|
||||
self.assertEqual(res['length'], len(buf))
|
||||
self.assertEqual(res['payload'], data)
|
||||
|
||||
def test_do_websocket_handshake(self):
|
||||
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
|
||||
soc.scheme = 'scheme'
|
||||
headers = {'Sec-WebSocket-Protocol': 'binary',
|
||||
'Sec-WebSocket-Version': '7',
|
||||
'Sec-WebSocket-Key': 'foo'}
|
||||
soc.do_websocket_handshake(headers, '127.0.0.1')
|
||||
def test_decode_hybi_multi(self):
|
||||
buf1 = b'\x01\x03\x48\x65\x6c'
|
||||
buf2 = b'\x80\x02\x6c\x6f'
|
||||
|
||||
def test_do_handshake(self):
|
||||
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
|
||||
self.stubs.Set(select, 'select', self._mock_select)
|
||||
self.stubs.Set(socket._socketobject, 'recv', lambda *args: 'mock_recv')
|
||||
self.assertRaises(Exception, soc.do_handshake, self.soc, '127.0.0.1')
|
||||
ws = websocket.WebSocket()
|
||||
|
||||
def test_do_handshake_ssl_error(self):
|
||||
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
|
||||
res1 = ws._decode_hybi(buf1)
|
||||
self.assertEqual(res1['fin'], 0)
|
||||
self.assertEqual(res1['opcode'], 0x1)
|
||||
self.assertEqual(res1['length'], len(buf1))
|
||||
self.assertEqual(res1['payload'], b'Hel')
|
||||
|
||||
def _mock_wrap_socket(*args, **kwargs):
|
||||
from ssl import SSLError
|
||||
raise SSLError('unit test exception')
|
||||
res2 = ws._decode_hybi(buf2)
|
||||
self.assertEqual(res2['fin'], 1)
|
||||
self.assertEqual(res2['opcode'], 0x0)
|
||||
self.assertEqual(res2['length'], len(buf2))
|
||||
self.assertEqual(res2['payload'], b'lo')
|
||||
|
||||
self.stubs.Set(select, 'select', self._mock_select)
|
||||
self.stubs.Set(socket._socketobject, 'recv', lambda *args: '\x16')
|
||||
self.stubs.Set(ssl, 'wrap_socket', _mock_wrap_socket)
|
||||
self.assertRaises(SSLError, soc.do_handshake, self.soc, '127.0.0.1')
|
||||
def test_encode_hybi_basic(self):
|
||||
ws = websocket.WebSocket()
|
||||
res = ws._encode_hybi(0x1, b'Hello')
|
||||
expected = b'\x81\x05\x48\x65\x6c\x6c\x6f'
|
||||
|
||||
def test_fallback_SIGCHILD(self):
|
||||
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
|
||||
soc.fallback_SIGCHLD(None, None)
|
||||
|
||||
def test_start_server_Exception(self):
|
||||
soc = self._get_websockserver(daemon=False, ssl_only=1, idle_timeout=1)
|
||||
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
|
||||
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
|
||||
lambda *args, **kwargs: None)
|
||||
self.stubs.Set(select, 'select', self._mock_select_exception)
|
||||
self.assertEqual(None, soc.start_server())
|
||||
|
||||
def test_start_server_KeyboardInterrupt(self):
|
||||
soc = self._get_websockserver(daemon=False, ssl_only=1, idle_timeout=1)
|
||||
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
|
||||
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
|
||||
lambda *args, **kwargs: None)
|
||||
self.stubs.Set(select, 'select', self._mock_select_keyboardinterrupt)
|
||||
self.assertEqual(None, soc.start_server())
|
||||
|
||||
def test_start_server_systemexit(self):
|
||||
websocket.ssl = None
|
||||
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
|
||||
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
|
||||
lambda *args, **kwargs: None)
|
||||
self.stubs.Set(select, 'select', self._mock_select_systemexit)
|
||||
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1,
|
||||
verbose=True)
|
||||
self.assertEqual(None, soc.start_server())
|
||||
|
||||
def test_WSRequestHandle_do_GET_nofile(self):
|
||||
request = 'GET /tmp.txt HTTP/0.9'
|
||||
with tempfile.NamedTemporaryFile() as test_file:
|
||||
test_file.write(request)
|
||||
test_file.flush()
|
||||
test_file.seek(0)
|
||||
con = MockConnection(test_file.name)
|
||||
soc = websocket.WSRequestHandler(con, "127.0.0.1", file_only=True)
|
||||
soc.path = ''
|
||||
soc.headers = {'upgrade': ''}
|
||||
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
|
||||
lambda *args: None)
|
||||
soc.do_GET()
|
||||
self.assertEqual(404, soc.last_code)
|
||||
|
||||
def test_WSRequestHandle_do_GET_hidden_resource(self):
|
||||
request = 'GET /tmp.txt HTTP/0.9'
|
||||
with tempfile.NamedTemporaryFile() as test_file:
|
||||
test_file.write(request)
|
||||
test_file.flush()
|
||||
test_file.seek(0)
|
||||
con = MockConnection(test_file.name)
|
||||
soc = websocket.WSRequestHandler(con, '127.0.0.1', no_parent=True)
|
||||
soc.path = test_file.name + '?'
|
||||
soc.headers = {'upgrade': ''}
|
||||
soc.webroot = 'no match startswith'
|
||||
self.stubs.Set(SimpleHTTPRequestHandler,
|
||||
'send_response',
|
||||
lambda *args: None)
|
||||
soc.do_GET()
|
||||
self.assertEqual(403, soc.last_code)
|
||||
|
||||
def testsocket_set_keepalive_options(self):
|
||||
keepcnt = 12
|
||||
keepidle = 34
|
||||
keepintvl = 56
|
||||
|
||||
sock = self.server.socket('localhost',
|
||||
tcp_keepcnt=keepcnt,
|
||||
tcp_keepidle=keepidle,
|
||||
tcp_keepintvl=keepintvl)
|
||||
|
||||
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPCNT), keepcnt)
|
||||
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPIDLE), keepidle)
|
||||
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPINTVL), keepintvl)
|
||||
|
||||
sock = self.server.socket('localhost',
|
||||
tcp_keepalive=False,
|
||||
tcp_keepcnt=keepcnt,
|
||||
tcp_keepidle=keepidle,
|
||||
tcp_keepintvl=keepintvl)
|
||||
|
||||
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPCNT), keepcnt)
|
||||
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPIDLE), keepidle)
|
||||
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPINTVL), keepintvl)
|
||||
self.assertEqual(res, expected)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright(c)2013 NTT corp. All Rights Reserved.
|
||||
# Copyright(c) 2015 Red Hat, Inc All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
@@ -15,113 +15,132 @@
|
||||
# under the License.
|
||||
|
||||
""" Unit tests for websocketproxy """
|
||||
import os
|
||||
import logging
|
||||
import select
|
||||
import shutil
|
||||
import stubout
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import unittest
|
||||
import unittest
|
||||
import socket
|
||||
|
||||
from mox3 import stubout
|
||||
|
||||
from websockify import websockifyserver
|
||||
from websockify import websocketproxy
|
||||
from websockify import token_plugins
|
||||
from websockify import auth_plugins
|
||||
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
BytesIO = StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
class MockSocket(object):
|
||||
def __init__(*args, **kwargs):
|
||||
class FakeSocket(object):
|
||||
def __init__(self, data=''):
|
||||
if isinstance(data, bytes):
|
||||
self._data = data
|
||||
else:
|
||||
self._data = data.encode('latin_1')
|
||||
|
||||
def recv(self, amt, flags=None):
|
||||
res = self._data[0:amt]
|
||||
if not (flags & socket.MSG_PEEK):
|
||||
self._data = self._data[amt:]
|
||||
|
||||
return res
|
||||
|
||||
def makefile(self, mode='r', buffsize=None):
|
||||
if 'b' in mode:
|
||||
return BytesIO(self._data)
|
||||
else:
|
||||
return StringIO(self._data.decode('latin_1'))
|
||||
|
||||
|
||||
class FakeServer(object):
|
||||
class EClose(Exception):
|
||||
pass
|
||||
|
||||
def shutdown(*args):
|
||||
pass
|
||||
|
||||
def close(*args):
|
||||
pass
|
||||
|
||||
|
||||
class WebSocketProxyTest(unittest.TestCase):
|
||||
|
||||
def _init_logger(self, tmpdir):
|
||||
name = 'websocket-unittest'
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.propagate = True
|
||||
filename = "%s.log" % (name)
|
||||
handler = logging.FileHandler(filename)
|
||||
handler.setFormatter(logging.Formatter("%(message)s"))
|
||||
logger.addHandler(handler)
|
||||
def __init__(self):
|
||||
self.token_plugin = None
|
||||
self.auth_plugin = None
|
||||
self.wrap_cmd = None
|
||||
self.ssl_target = None
|
||||
self.unix_target = None
|
||||
|
||||
class ProxyRequestHandlerTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Called automatically before each test."""
|
||||
super(WebSocketProxyTest, self).setUp()
|
||||
self.soc = ''
|
||||
super(ProxyRequestHandlerTestCase, self).setUp()
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
# Temporary dir for test data
|
||||
self.tmpdir = tempfile.mkdtemp()
|
||||
# Put log somewhere persistent
|
||||
self._init_logger('./')
|
||||
# Mock this out cause it screws tests up
|
||||
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
|
||||
self.handler = websocketproxy.ProxyRequestHandler(
|
||||
FakeSocket(''), "127.0.0.1", FakeServer())
|
||||
self.handler.path = "https://localhost:6080/websockify?token=blah"
|
||||
self.handler.headers = None
|
||||
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket',
|
||||
staticmethod(lambda *args, **kwargs: None))
|
||||
|
||||
def tearDown(self):
|
||||
"""Called automatically after each test."""
|
||||
self.stubs.UnsetAll()
|
||||
shutil.rmtree(self.tmpdir)
|
||||
super(WebSocketProxyTest, self).tearDown()
|
||||
super(ProxyRequestHandlerTestCase, self).tearDown()
|
||||
|
||||
def _get_websockproxy(self, **kwargs):
|
||||
return websocketproxy.WebSocketProxy(key=self.tmpdir,
|
||||
web=self.tmpdir,
|
||||
record=self.tmpdir,
|
||||
**kwargs)
|
||||
def test_get_target(self):
|
||||
class TestPlugin(token_plugins.BasePlugin):
|
||||
def lookup(self, token):
|
||||
return ("some host", "some port")
|
||||
|
||||
def test_run_wrap_cmd(self):
|
||||
web_socket_proxy = self._get_websockproxy()
|
||||
web_socket_proxy.__dict__["wrap_cmd"] = "wrap_cmd"
|
||||
host, port = self.handler.get_target(
|
||||
TestPlugin(None), self.handler.path)
|
||||
|
||||
def mock_Popen(*args, **kwargs):
|
||||
return '_mock_cmd'
|
||||
self.assertEqual(host, "some host")
|
||||
self.assertEqual(port, "some port")
|
||||
|
||||
self.stubs.Set(subprocess, 'Popen', mock_Popen)
|
||||
web_socket_proxy.run_wrap_cmd()
|
||||
self.assertEquals(web_socket_proxy.spawn_message, True)
|
||||
def test_get_target_unix_socket(self):
|
||||
class TestPlugin(token_plugins.BasePlugin):
|
||||
def lookup(self, token):
|
||||
return ("unix_socket", "/tmp/socket")
|
||||
|
||||
def test_started(self):
|
||||
web_socket_proxy = self._get_websockproxy()
|
||||
web_socket_proxy.__dict__["spawn_message"] = False
|
||||
web_socket_proxy.__dict__["wrap_cmd"] = "wrap_cmd"
|
||||
_, socket = self.handler.get_target(
|
||||
TestPlugin(None), self.handler.path)
|
||||
|
||||
def mock_run_wrap_cmd(*args, **kwargs):
|
||||
web_socket_proxy.__dict__["spawn_message"] = True
|
||||
self.assertEqual(socket, "/tmp/socket")
|
||||
|
||||
self.stubs.Set(web_socket_proxy, 'run_wrap_cmd', mock_run_wrap_cmd)
|
||||
web_socket_proxy.started()
|
||||
self.assertEquals(web_socket_proxy.__dict__["spawn_message"], True)
|
||||
def test_get_target_raises_error_on_unknown_token(self):
|
||||
class TestPlugin(token_plugins.BasePlugin):
|
||||
def lookup(self, token):
|
||||
return None
|
||||
|
||||
def test_poll(self):
|
||||
web_socket_proxy = self._get_websockproxy()
|
||||
web_socket_proxy.__dict__["wrap_cmd"] = "wrap_cmd"
|
||||
web_socket_proxy.__dict__["wrap_mode"] = "respawn"
|
||||
web_socket_proxy.__dict__["wrap_times"] = [99999999]
|
||||
web_socket_proxy.__dict__["spawn_message"] = True
|
||||
web_socket_proxy.__dict__["cmd"] = None
|
||||
self.stubs.Set(time, 'time', lambda: 100000000.000)
|
||||
web_socket_proxy.poll()
|
||||
self.assertEquals(web_socket_proxy.spawn_message, False)
|
||||
self.assertRaises(FakeServer.EClose, self.handler.get_target,
|
||||
TestPlugin(None), "https://localhost:6080/websockify?token=blah")
|
||||
|
||||
def test_new_client(self):
|
||||
web_socket_proxy = self._get_websockproxy()
|
||||
web_socket_proxy.__dict__["verbose"] = "verbose"
|
||||
web_socket_proxy.__dict__["daemon"] = None
|
||||
web_socket_proxy.__dict__["client"] = "client"
|
||||
def test_token_plugin(self):
|
||||
class TestPlugin(token_plugins.BasePlugin):
|
||||
def lookup(self, token):
|
||||
return (self.source + token).split(',')
|
||||
|
||||
self.stubs.Set(web_socket_proxy, 'socket', MockSocket)
|
||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
|
||||
staticmethod(lambda *args, **kwargs: None))
|
||||
|
||||
def mock_select(*args, **kwargs):
|
||||
ins = None
|
||||
outs = None
|
||||
excepts = "excepts"
|
||||
return ins, outs, excepts
|
||||
self.handler.server.token_plugin = TestPlugin("somehost,")
|
||||
self.handler.validate_connection()
|
||||
|
||||
self.assertEqual(self.handler.server.target_host, "somehost")
|
||||
self.assertEqual(self.handler.server.target_port, "blah")
|
||||
|
||||
def test_auth_plugin(self):
|
||||
class TestPlugin(auth_plugins.BasePlugin):
|
||||
def authenticate(self, headers, target_host, target_port):
|
||||
if target_host == self.source:
|
||||
raise auth_plugins.AuthenticationError(response_msg="some_error")
|
||||
|
||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
|
||||
staticmethod(lambda *args, **kwargs: None))
|
||||
|
||||
self.handler.server.auth_plugin = TestPlugin("somehost")
|
||||
self.handler.server.target_host = "somehost"
|
||||
self.handler.server.target_port = "someport"
|
||||
|
||||
self.assertRaises(auth_plugins.AuthenticationError,
|
||||
self.handler.validate_connection)
|
||||
|
||||
self.handler.server.target_host = "someotherhost"
|
||||
self.handler.validate_connection()
|
||||
|
||||
self.stubs.Set(select, 'select', mock_select)
|
||||
self.assertRaises(Exception, web_socket_proxy.new_websocket_client)
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright(c)2013 NTT corp. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
""" Unit tests for websockifyserver """
|
||||
import errno
|
||||
import os
|
||||
import logging
|
||||
import select
|
||||
import shutil
|
||||
import socket
|
||||
import ssl
|
||||
from mox3 import stubout
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
import socket
|
||||
import signal
|
||||
from websockify import websockifyserver
|
||||
|
||||
try:
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
except ImportError:
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
BytesIO = StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
|
||||
|
||||
def raise_oserror(*args, **kwargs):
|
||||
raise OSError('fake error')
|
||||
|
||||
|
||||
class FakeSocket(object):
|
||||
def __init__(self, data=''):
|
||||
if isinstance(data, bytes):
|
||||
self._data = data
|
||||
else:
|
||||
self._data = data.encode('latin_1')
|
||||
|
||||
def recv(self, amt, flags=None):
|
||||
res = self._data[0:amt]
|
||||
if not (flags & socket.MSG_PEEK):
|
||||
self._data = self._data[amt:]
|
||||
|
||||
return res
|
||||
|
||||
def makefile(self, mode='r', buffsize=None):
|
||||
if 'b' in mode:
|
||||
return BytesIO(self._data)
|
||||
else:
|
||||
return StringIO(self._data.decode('latin_1'))
|
||||
|
||||
|
||||
class WebSockifyRequestHandlerTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(WebSockifyRequestHandlerTestCase, self).setUp()
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
|
||||
# Mock this out cause it screws tests up
|
||||
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
|
||||
self.stubs.Set(BaseHTTPRequestHandler, 'send_response',
|
||||
lambda *args, **kwargs: None)
|
||||
|
||||
def tearDown(self):
|
||||
"""Called automatically after each test."""
|
||||
self.stubs.UnsetAll()
|
||||
os.rmdir(self.tmpdir)
|
||||
super(WebSockifyRequestHandlerTestCase, self).tearDown()
|
||||
|
||||
def _get_server(self, handler_class=websockifyserver.WebSockifyRequestHandler,
|
||||
**kwargs):
|
||||
web = kwargs.pop('web', self.tmpdir)
|
||||
return websockifyserver.WebSockifyServer(
|
||||
handler_class, listen_host='localhost',
|
||||
listen_port=80, key=self.tmpdir, web=web,
|
||||
record=self.tmpdir, daemon=False, ssl_only=0, idle_timeout=1,
|
||||
**kwargs)
|
||||
|
||||
def test_normal_get_with_only_upgrade_returns_error(self):
|
||||
server = self._get_server(web=None)
|
||||
handler = websockifyserver.WebSockifyRequestHandler(
|
||||
FakeSocket('GET /tmp.txt HTTP/1.1'), '127.0.0.1', server)
|
||||
|
||||
def fake_send_response(self, code, message=None):
|
||||
self.last_code = code
|
||||
|
||||
self.stubs.Set(BaseHTTPRequestHandler, 'send_response',
|
||||
fake_send_response)
|
||||
|
||||
handler.do_GET()
|
||||
self.assertEqual(handler.last_code, 405)
|
||||
|
||||
def test_list_dir_with_file_only_returns_error(self):
|
||||
server = self._get_server(file_only=True)
|
||||
handler = websockifyserver.WebSockifyRequestHandler(
|
||||
FakeSocket('GET / HTTP/1.1'), '127.0.0.1', server)
|
||||
|
||||
def fake_send_response(self, code, message=None):
|
||||
self.last_code = code
|
||||
|
||||
self.stubs.Set(BaseHTTPRequestHandler, 'send_response',
|
||||
fake_send_response)
|
||||
|
||||
handler.path = '/'
|
||||
handler.do_GET()
|
||||
self.assertEqual(handler.last_code, 404)
|
||||
|
||||
|
||||
class WebSockifyServerTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(WebSockifyServerTestCase, self).setUp()
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
|
||||
# Mock this out cause it screws tests up
|
||||
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
|
||||
|
||||
def tearDown(self):
|
||||
"""Called automatically after each test."""
|
||||
self.stubs.UnsetAll()
|
||||
os.rmdir(self.tmpdir)
|
||||
super(WebSockifyServerTestCase, self).tearDown()
|
||||
|
||||
def _get_server(self, handler_class=websockifyserver.WebSockifyRequestHandler,
|
||||
**kwargs):
|
||||
return websockifyserver.WebSockifyServer(
|
||||
handler_class, listen_host='localhost',
|
||||
listen_port=80, key=self.tmpdir, web=self.tmpdir,
|
||||
record=self.tmpdir, **kwargs)
|
||||
|
||||
def test_daemonize_raises_error_while_closing_fds(self):
|
||||
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
|
||||
self.stubs.Set(os, 'fork', lambda *args: 0)
|
||||
self.stubs.Set(signal, 'signal', lambda *args: None)
|
||||
self.stubs.Set(os, 'setsid', lambda *args: None)
|
||||
self.stubs.Set(os, 'close', raise_oserror)
|
||||
self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./')
|
||||
|
||||
def test_daemonize_ignores_ebadf_error_while_closing_fds(self):
|
||||
def raise_oserror_ebadf(fd):
|
||||
raise OSError(errno.EBADF, 'fake error')
|
||||
|
||||
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
|
||||
self.stubs.Set(os, 'fork', lambda *args: 0)
|
||||
self.stubs.Set(os, 'setsid', lambda *args: None)
|
||||
self.stubs.Set(signal, 'signal', lambda *args: None)
|
||||
self.stubs.Set(os, 'close', raise_oserror_ebadf)
|
||||
self.stubs.Set(os, 'open', raise_oserror)
|
||||
self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./')
|
||||
|
||||
def test_handshake_fails_on_not_ready(self):
|
||||
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1)
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([], [], [])
|
||||
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
self.assertRaises(
|
||||
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
|
||||
FakeSocket(), '127.0.0.1')
|
||||
|
||||
def test_empty_handshake_fails(self):
|
||||
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1)
|
||||
|
||||
sock = FakeSocket('')
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
self.assertRaises(
|
||||
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
|
||||
sock, '127.0.0.1')
|
||||
|
||||
def test_handshake_policy_request(self):
|
||||
# TODO(directxman12): implement
|
||||
pass
|
||||
|
||||
def test_handshake_ssl_only_without_ssl_raises_error(self):
|
||||
server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1)
|
||||
|
||||
sock = FakeSocket('some initial data')
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
self.assertRaises(
|
||||
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
|
||||
sock, '127.0.0.1')
|
||||
|
||||
def test_do_handshake_no_ssl(self):
|
||||
class FakeHandler(object):
|
||||
CALLED = False
|
||||
def __init__(self, *args, **kwargs):
|
||||
type(self).CALLED = True
|
||||
|
||||
FakeHandler.CALLED = False
|
||||
|
||||
server = self._get_server(
|
||||
handler_class=FakeHandler, daemon=True,
|
||||
ssl_only=0, idle_timeout=1)
|
||||
|
||||
sock = FakeSocket('some initial data')
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
self.assertEqual(server.do_handshake(sock, '127.0.0.1'), sock)
|
||||
self.assertTrue(FakeHandler.CALLED, True)
|
||||
|
||||
def test_do_handshake_ssl(self):
|
||||
# TODO(directxman12): implement this
|
||||
pass
|
||||
|
||||
def test_do_handshake_ssl_without_ssl_raises_error(self):
|
||||
# TODO(directxman12): implement this
|
||||
pass
|
||||
|
||||
def test_do_handshake_ssl_without_cert_raises_error(self):
|
||||
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1,
|
||||
cert='afdsfasdafdsafdsafdsafdas')
|
||||
|
||||
sock = FakeSocket("\x16some ssl data")
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
self.assertRaises(
|
||||
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
|
||||
sock, '127.0.0.1')
|
||||
|
||||
def test_do_handshake_ssl_error_eof_raises_close_error(self):
|
||||
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1)
|
||||
|
||||
sock = FakeSocket("\x16some ssl data")
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
def fake_wrap_socket(*args, **kwargs):
|
||||
raise ssl.SSLError(ssl.SSL_ERROR_EOF)
|
||||
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
self.stubs.Set(ssl, 'wrap_socket', fake_wrap_socket)
|
||||
self.assertRaises(
|
||||
websockifyserver.WebSockifyServer.EClose, server.do_handshake,
|
||||
sock, '127.0.0.1')
|
||||
|
||||
def test_fallback_sigchld_handler(self):
|
||||
# TODO(directxman12): implement this
|
||||
pass
|
||||
|
||||
def test_start_server_error(self):
|
||||
server = self._get_server(daemon=False, ssl_only=1, idle_timeout=1)
|
||||
sock = server.socket('localhost')
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
raise Exception("fake error")
|
||||
|
||||
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket',
|
||||
lambda *args, **kwargs: sock)
|
||||
self.stubs.Set(websockifyserver.WebSockifyServer, 'daemonize',
|
||||
lambda *args, **kwargs: None)
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
server.start_server()
|
||||
|
||||
def test_start_server_keyboardinterrupt(self):
|
||||
server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1)
|
||||
sock = server.socket('localhost')
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
raise KeyboardInterrupt
|
||||
|
||||
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket',
|
||||
lambda *args, **kwargs: sock)
|
||||
self.stubs.Set(websockifyserver.WebSockifyServer, 'daemonize',
|
||||
lambda *args, **kwargs: None)
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
server.start_server()
|
||||
|
||||
def test_start_server_systemexit(self):
|
||||
server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1)
|
||||
sock = server.socket('localhost')
|
||||
|
||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
||||
sys.exit()
|
||||
|
||||
self.stubs.Set(websockifyserver.WebSockifyServer, 'socket',
|
||||
lambda *args, **kwargs: sock)
|
||||
self.stubs.Set(websockifyserver.WebSockifyServer, 'daemonize',
|
||||
lambda *args, **kwargs: None)
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
server.start_server()
|
||||
|
||||
def test_socket_set_keepalive_options(self):
|
||||
keepcnt = 12
|
||||
keepidle = 34
|
||||
keepintvl = 56
|
||||
|
||||
server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1)
|
||||
sock = server.socket('localhost',
|
||||
tcp_keepcnt=keepcnt,
|
||||
tcp_keepidle=keepidle,
|
||||
tcp_keepintvl=keepintvl)
|
||||
|
||||
if hasattr(socket, 'TCP_KEEPCNT'):
|
||||
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPCNT), keepcnt)
|
||||
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPIDLE), keepidle)
|
||||
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPINTVL), keepintvl)
|
||||
|
||||
sock = server.socket('localhost',
|
||||
tcp_keepalive=False,
|
||||
tcp_keepcnt=keepcnt,
|
||||
tcp_keepidle=keepidle,
|
||||
tcp_keepintvl=keepintvl)
|
||||
|
||||
if hasattr(socket, 'TCP_KEEPCNT'):
|
||||
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPCNT), keepcnt)
|
||||
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPIDLE), keepidle)
|
||||
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPINTVL), keepintvl)
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
'''
|
||||
Display UTF-8 encoding for 0-255.'''
|
||||
|
||||
import sys, os, socket, ssl, time, traceback
|
||||
from select import select
|
||||
sys.path.insert(0,os.path.join(os.path.dirname(__file__), ".."))
|
||||
from websockify.websocket import WebSocketServer
|
||||
|
||||
if __name__ == '__main__':
|
||||
print "val: hybi_base64 | hybi_binary"
|
||||
for c in range(0, 256):
|
||||
hybi_base64 = WebSocketServer.encode_hybi(chr(c), opcode=1,
|
||||
base64=True)
|
||||
hybi_binary = WebSocketServer.encode_hybi(chr(c), opcode=2,
|
||||
base64=False)
|
||||
print "%d: %s | %s" % (c, repr(hybi_base64), repr(hybi_binary))
|
||||
|
||||
@@ -4,17 +4,14 @@
|
||||
# and then run "tox" from this directory.
|
||||
|
||||
[tox]
|
||||
envlist = py24,py25,py26,py27,py30
|
||||
setupdir = ../
|
||||
envlist = py24,py26,py27,py33,py34
|
||||
|
||||
[testenv]
|
||||
commands = nosetests {posargs}
|
||||
deps =
|
||||
mox
|
||||
nose
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
|
||||
# At some point we should enable this since tox epdctes it to exist but
|
||||
# the code will need pep8ising first.
|
||||
# At some point we should enable this since tox expects it to exist but
|
||||
# the code will need pep8ising first.
|
||||
#[testenv:pep8]
|
||||
#commands = flake8
|
||||
#dep = flake8
|
||||
@@ -0,0 +1,83 @@
|
||||
class BasePlugin(object):
|
||||
def __init__(self, src=None):
|
||||
self.source = src
|
||||
|
||||
def authenticate(self, headers, target_host, target_port):
|
||||
pass
|
||||
|
||||
|
||||
class AuthenticationError(Exception):
|
||||
def __init__(self, log_msg=None, response_code=403, response_headers={}, response_msg=None):
|
||||
self.code = response_code
|
||||
self.headers = response_headers
|
||||
self.msg = response_msg
|
||||
|
||||
if log_msg is None:
|
||||
log_msg = response_msg
|
||||
|
||||
super(AuthenticationError, self).__init__('%s %s' % (self.code, log_msg))
|
||||
|
||||
|
||||
class InvalidOriginError(AuthenticationError):
|
||||
def __init__(self, expected, actual):
|
||||
self.expected_origin = expected
|
||||
self.actual_origin = actual
|
||||
|
||||
super(InvalidOriginError, self).__init__(
|
||||
response_msg='Invalid Origin',
|
||||
log_msg="Invalid Origin Header: Expected one of "
|
||||
"%s, got '%s'" % (expected, actual))
|
||||
|
||||
|
||||
class BasicHTTPAuth(object):
|
||||
"""Verifies Basic Auth headers. Specify src as username:password"""
|
||||
|
||||
def __init__(self, src=None):
|
||||
self.src = src
|
||||
|
||||
def authenticate(self, headers, target_host, target_port):
|
||||
import base64
|
||||
auth_header = headers.get('Authorization')
|
||||
if auth_header:
|
||||
if not auth_header.startswith('Basic '):
|
||||
raise AuthenticationError(response_code=403)
|
||||
|
||||
try:
|
||||
user_pass_raw = base64.b64decode(auth_header[6:])
|
||||
except TypeError:
|
||||
raise AuthenticationError(response_code=403)
|
||||
|
||||
try:
|
||||
# http://stackoverflow.com/questions/7242316/what-encoding-should-i-use-for-http-basic-authentication
|
||||
user_pass_as_text = user_pass_raw.decode('ISO-8859-1')
|
||||
except UnicodeDecodeError:
|
||||
raise AuthenticationError(response_code=403)
|
||||
|
||||
user_pass = user_pass_as_text.split(':', 1)
|
||||
if len(user_pass) != 2:
|
||||
raise AuthenticationError(response_code=403)
|
||||
|
||||
if not self.validate_creds(*user_pass):
|
||||
raise AuthenticationError(response_code=403)
|
||||
|
||||
else:
|
||||
raise AuthenticationError(response_code=401,
|
||||
response_headers={'WWW-Authenticate': 'Basic realm="Websockify"'})
|
||||
|
||||
def validate_creds(self, username, password):
|
||||
if '%s:%s' % (username, password) == self.src:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
class ExpectOrigin(object):
|
||||
def __init__(self, src=None):
|
||||
if src is None:
|
||||
self.source = []
|
||||
else:
|
||||
self.source = src.split()
|
||||
|
||||
def authenticate(self, headers, target_host, target_port):
|
||||
origin = headers.get('Origin', None)
|
||||
if origin is None or origin not in self.source:
|
||||
raise InvalidOriginError(expected=self.source, actual=origin)
|
||||
@@ -12,6 +12,10 @@ class ReadOnlyTokenFile(BasePlugin):
|
||||
# source is a token file with lines like
|
||||
# token: host:port
|
||||
# or a directory of such files
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ReadOnlyTokenFile, self).__init__(*args, **kwargs)
|
||||
self._targets = None
|
||||
|
||||
def _load_targets(self):
|
||||
if os.path.isdir(self.source):
|
||||
cfg_files = [os.path.join(self.source, f) for
|
||||
@@ -24,7 +28,7 @@ class ReadOnlyTokenFile(BasePlugin):
|
||||
for line in [l.strip() for l in open(f).readlines()]:
|
||||
if line and not line.startswith('#'):
|
||||
tok, target = line.split(': ')
|
||||
self._targets[tok] = target.strip().split(':')
|
||||
self._targets[tok] = target.strip().rsplit(':', 1)
|
||||
|
||||
def lookup(self, token):
|
||||
if self._targets is None:
|
||||
@@ -75,4 +79,5 @@ class JSONTokenApi(BaseTokenAPI):
|
||||
# should go
|
||||
|
||||
def process_result(self, resp):
|
||||
return (resp.json['host'], resp.json['port'])
|
||||
resp_json = resp.json()
|
||||
return (resp_json['host'], resp_json['port'])
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,20 +11,23 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||
|
||||
'''
|
||||
|
||||
import signal, socket, optparse, time, os, sys, subprocess, logging
|
||||
import signal, socket, optparse, time, os, sys, subprocess, logging, errno
|
||||
try: from socketserver import ForkingMixIn
|
||||
except: from SocketServer import ForkingMixIn
|
||||
try: from http.server import HTTPServer
|
||||
except: from BaseHTTPServer import HTTPServer
|
||||
from select import select
|
||||
from websockify import websocket
|
||||
import select
|
||||
from websockify import websockifyserver
|
||||
from websockify import auth_plugins as auth
|
||||
try:
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
except:
|
||||
from cgi import parse_qs
|
||||
from urlparse import urlparse
|
||||
|
||||
class ProxyRequestHandler(websocket.WebSocketRequestHandler):
|
||||
class ProxyRequestHandler(websockifyserver.WebSockifyRequestHandler):
|
||||
|
||||
buffer_size = 65536
|
||||
|
||||
traffic_legend = """
|
||||
Traffic Legend:
|
||||
@@ -37,15 +40,40 @@ Traffic Legend:
|
||||
< - Client send
|
||||
<. - Client send partial
|
||||
"""
|
||||
|
||||
def send_auth_error(self, ex):
|
||||
self.send_response(ex.code, ex.msg)
|
||||
self.send_header('Content-Type', 'text/html')
|
||||
for name, val in ex.headers.items():
|
||||
self.send_header(name, val)
|
||||
|
||||
self.end_headers()
|
||||
|
||||
def validate_connection(self):
|
||||
if self.server.token_plugin:
|
||||
host, port = self.get_target(self.server.token_plugin, self.path)
|
||||
if host == 'unix_socket':
|
||||
self.server.unix_target = port
|
||||
|
||||
else:
|
||||
self.server.target_host = host
|
||||
self.server.target_port = port
|
||||
|
||||
if self.server.auth_plugin:
|
||||
try:
|
||||
self.server.auth_plugin.authenticate(
|
||||
headers=self.headers, target_host=self.server.target_host,
|
||||
target_port=self.server.target_port)
|
||||
except auth.AuthenticationError:
|
||||
ex = sys.exc_info()[1]
|
||||
self.send_auth_error(ex)
|
||||
raise
|
||||
|
||||
def new_websocket_client(self):
|
||||
"""
|
||||
Called after a new WebSocket connection has been established.
|
||||
"""
|
||||
# Checks if we receive a token, and look
|
||||
# for a valid target for it then
|
||||
if self.server.token_plugin:
|
||||
(self.server.target_host, self.server.target_port) = self.get_target(self.server.token_plugin, self.path)
|
||||
# Checking for a token is done in validate_connection()
|
||||
|
||||
# Connect to the target
|
||||
if self.server.wrap_cmd:
|
||||
@@ -60,9 +88,15 @@ Traffic Legend:
|
||||
msg += " (using SSL)"
|
||||
self.log_message(msg)
|
||||
|
||||
tsock = websocket.WebSocketServer.socket(self.server.target_host,
|
||||
self.server.target_port,
|
||||
connect=True, use_ssl=self.server.ssl_target, unix_socket=self.server.unix_target)
|
||||
tsock = websockifyserver.WebSockifyServer.socket(self.server.target_host,
|
||||
self.server.target_port,
|
||||
connect=True,
|
||||
use_ssl=self.server.ssl_target,
|
||||
unix_socket=self.server.unix_target)
|
||||
|
||||
self.request.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
|
||||
if not self.server.wrap_cmd and not self.server.unix_target:
|
||||
tsock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
|
||||
|
||||
self.print_traffic(self.traffic_legend)
|
||||
|
||||
@@ -128,7 +162,20 @@ Traffic Legend:
|
||||
|
||||
if tqueue: wlist.append(target)
|
||||
if cqueue or c_pend: wlist.append(self.request)
|
||||
ins, outs, excepts = select(rlist, wlist, [], 1)
|
||||
try:
|
||||
ins, outs, excepts = select.select(rlist, wlist, [], 1)
|
||||
except (select.error, OSError):
|
||||
exc = sys.exc_info()[1]
|
||||
if hasattr(exc, 'errno'):
|
||||
err = exc.errno
|
||||
else:
|
||||
err = exc[0]
|
||||
|
||||
if err != errno.EINTR:
|
||||
raise
|
||||
else:
|
||||
continue
|
||||
|
||||
if excepts: raise Exception("Socket exception")
|
||||
|
||||
if self.request in outs:
|
||||
@@ -174,12 +221,10 @@ Traffic Legend:
|
||||
cqueue.append(buf)
|
||||
self.print_traffic("{")
|
||||
|
||||
class WebSocketProxy(websocket.WebSocketServer):
|
||||
class WebSocketProxy(websockifyserver.WebSockifyServer):
|
||||
"""
|
||||
Proxy traffic to and from a WebSockets client to a normal TCP
|
||||
socket server target. All traffic to/from the client is base64
|
||||
encoded/decoded to allow binary data to be sent/received to/from
|
||||
the target.
|
||||
socket server target.
|
||||
"""
|
||||
|
||||
buffer_size = 65536
|
||||
@@ -194,21 +239,8 @@ class WebSocketProxy(websocket.WebSocketServer):
|
||||
self.ssl_target = kwargs.pop('ssl_target', None)
|
||||
self.heartbeat = kwargs.pop('heartbeat', None)
|
||||
|
||||
token_plugin = kwargs.pop('token_plugin', None)
|
||||
token_source = kwargs.pop('token_source', None)
|
||||
|
||||
if token_plugin is not None:
|
||||
if '.' not in token_plugin:
|
||||
token_plugin = 'websockify.token_plugins.%s' % token_plugin
|
||||
|
||||
token_plugin_module, token_plugin_cls = token_plugin.rsplit('.', 1)
|
||||
|
||||
__import__(token_plugin_module)
|
||||
token_plugin_cls = getattr(sys.modules[token_plugin_module], token_plugin_cls)
|
||||
|
||||
self.token_plugin = token_plugin_cls(token_source)
|
||||
else:
|
||||
self.token_plugin = None
|
||||
self.token_plugin = kwargs.pop('token_plugin', None)
|
||||
self.auth_plugin = kwargs.pop('auth_plugin', None)
|
||||
|
||||
# Last 3 timestamps command was run
|
||||
self.wrap_times = [0, 0, 0]
|
||||
@@ -242,7 +274,7 @@ class WebSocketProxy(websocket.WebSocketServer):
|
||||
"REBIND_OLD_PORT": str(kwargs['listen_port']),
|
||||
"REBIND_NEW_PORT": str(self.target_port)})
|
||||
|
||||
websocket.WebSocketServer.__init__(self, RequestHandlerClass, *args, **kwargs)
|
||||
websockifyserver.WebSockifyServer.__init__(self, RequestHandlerClass, *args, **kwargs)
|
||||
|
||||
def run_wrap_cmd(self):
|
||||
self.msg("Starting '%s'", " ".join(self.wrap_cmd))
|
||||
@@ -381,19 +413,40 @@ def websockify_init():
|
||||
parser.add_option("--token-source", default=None, metavar="ARG",
|
||||
help="an argument to be passed to the token plugin"
|
||||
"on instantiation")
|
||||
parser.add_option("--auto-pong", action="store_true",
|
||||
help="Automatically respond to ping frames with a pong")
|
||||
parser.add_option("--auth-plugin", default=None, metavar="PLUGIN",
|
||||
help="use the given Python class to determine if "
|
||||
"a connection is allowed")
|
||||
parser.add_option("--auth-source", default=None, metavar="ARG",
|
||||
help="an argument to be passed to the auth plugin"
|
||||
"on instantiation")
|
||||
parser.add_option("--heartbeat", type=int, default=0,
|
||||
help="send a ping to the client every HEARTBEAT seconds")
|
||||
parser.add_option("--log-file", metavar="FILE",
|
||||
dest="log_file",
|
||||
help="File where logs will be saved")
|
||||
|
||||
|
||||
(opts, args) = parser.parse_args()
|
||||
|
||||
if opts.log_file:
|
||||
opts.log_file = os.path.abspath(opts.log_file)
|
||||
handler = logging.FileHandler(opts.log_file)
|
||||
handler.setLevel(logging.DEBUG)
|
||||
handler.setFormatter(logging.Formatter("%(message)s"))
|
||||
logging.getLogger(WebSocketProxy.log_prefix).addHandler(handler)
|
||||
|
||||
del opts.log_file
|
||||
|
||||
if opts.verbose:
|
||||
logging.getLogger(WebSocketProxy.log_prefix).setLevel(logging.DEBUG)
|
||||
|
||||
if opts.token_source and not opts.token_plugin:
|
||||
parser.error("You must use --token-plugin to use --token-source")
|
||||
|
||||
if opts.auth_source and not opts.auth_plugin:
|
||||
parser.error("You must use --auth-plugin to use --auth-source")
|
||||
|
||||
|
||||
# Transform to absolute path as daemon may chdir
|
||||
if opts.target_cfg:
|
||||
opts.target_cfg = os.path.abspath(opts.target_cfg)
|
||||
@@ -414,7 +467,7 @@ def websockify_init():
|
||||
if len(args) > 2:
|
||||
parser.error("Too many arguments")
|
||||
|
||||
if not websocket.ssl and opts.ssl_target:
|
||||
if not websockifyserver.ssl and opts.ssl_target:
|
||||
parser.error("SSL target requested and Python SSL module not loaded.");
|
||||
|
||||
if opts.ssl_only and not os.path.exists(opts.cert):
|
||||
@@ -442,6 +495,33 @@ def websockify_init():
|
||||
try: opts.target_port = int(opts.target_port)
|
||||
except: parser.error("Error parsing target port")
|
||||
|
||||
if opts.token_plugin is not None:
|
||||
if '.' not in opts.token_plugin:
|
||||
opts.token_plugin = (
|
||||
'websockify.token_plugins.%s' % opts.token_plugin)
|
||||
|
||||
token_plugin_module, token_plugin_cls = opts.token_plugin.rsplit('.', 1)
|
||||
|
||||
__import__(token_plugin_module)
|
||||
token_plugin_cls = getattr(sys.modules[token_plugin_module], token_plugin_cls)
|
||||
|
||||
opts.token_plugin = token_plugin_cls(opts.token_source)
|
||||
|
||||
del opts.token_source
|
||||
|
||||
if opts.auth_plugin is not None:
|
||||
if '.' not in opts.auth_plugin:
|
||||
opts.auth_plugin = 'websockify.auth_plugins.%s' % opts.auth_plugin
|
||||
|
||||
auth_plugin_module, auth_plugin_cls = opts.auth_plugin.rsplit('.', 1)
|
||||
|
||||
__import__(auth_plugin_module)
|
||||
auth_plugin_cls = getattr(sys.modules[auth_plugin_module], auth_plugin_cls)
|
||||
|
||||
opts.auth_plugin = auth_plugin_cls(opts.auth_source)
|
||||
|
||||
del opts.auth_source
|
||||
|
||||
# Create and start the WebSockets proxy
|
||||
libserver = opts.libserver
|
||||
del opts.libserver
|
||||
@@ -470,10 +550,11 @@ class LibProxyServer(ForkingMixIn, HTTPServer):
|
||||
self.unix_target = kwargs.pop('unix_target', None)
|
||||
self.ssl_target = kwargs.pop('ssl_target', None)
|
||||
self.token_plugin = kwargs.pop('token_plugin', None)
|
||||
self.token_source = kwargs.pop('token_source', None)
|
||||
self.auth_plugin = kwargs.pop('auth_plugin', None)
|
||||
self.heartbeat = kwargs.pop('heartbeat', None)
|
||||
|
||||
self.token_plugin = None
|
||||
self.auth_plugin = None
|
||||
self.daemon = False
|
||||
|
||||
# Server configuration
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Python WebSocket server base
|
||||
Copyright 2011 Joel Martin
|
||||
Copyright 2016 Pierre Ossman
|
||||
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
# python 3.0 differences
|
||||
try: from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
except: from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
from websockify.websocket import WebSocket, WebSocketWantReadError, WebSocketWantWriteError
|
||||
|
||||
class WebSocketRequestHandler(BaseHTTPRequestHandler):
|
||||
"""WebSocket request handler base class.
|
||||
|
||||
This class forms the base for a server that wishes to handle
|
||||
WebSocket requests. It functions exactly as BastHTTPRequestHandler,
|
||||
except that WebSocket requests are intercepted and the methods
|
||||
handle_upgrade() and handle_websocket() are called. The standard
|
||||
do_GET() will be called for normal requests.
|
||||
|
||||
The class instance SocketClass can be overridden with the class to
|
||||
use for the WebSocket connection.
|
||||
"""
|
||||
|
||||
SocketClass = WebSocket
|
||||
|
||||
def __init__(self, request, client_address, server):
|
||||
BaseHTTPRequestHandler.__init__(self, request, client_address, server)
|
||||
|
||||
def handle_one_request(self):
|
||||
"""Extended request handler
|
||||
|
||||
This is where WebSocketRequestHandler redirects requests to the
|
||||
new methods. Any sub-classes must call this method in order for
|
||||
the calls to function.
|
||||
"""
|
||||
self._real_do_GET = self.do_GET
|
||||
self.do_GET = self._websocket_do_GET
|
||||
try:
|
||||
BaseHTTPRequestHandler.handle_one_request(self)
|
||||
finally:
|
||||
self.do_GET = self._real_do_GET
|
||||
|
||||
def _websocket_do_GET(self):
|
||||
# Checks if it is a websocket request and redirects
|
||||
self.do_GET = self._real_do_GET
|
||||
|
||||
if (self.headers.get('upgrade') and
|
||||
self.headers.get('upgrade').lower() == 'websocket'):
|
||||
self.handle_upgrade()
|
||||
else:
|
||||
self.do_GET()
|
||||
|
||||
def handle_upgrade(self):
|
||||
"""Initial handler for a WebSocket request
|
||||
|
||||
This method is called when a WebSocket is requested. By default
|
||||
it will create a WebSocket object and perform the negotiation.
|
||||
The WebSocket object will then replace the request object and
|
||||
handle_websocket() will be called.
|
||||
"""
|
||||
websocket = self.SocketClass()
|
||||
try:
|
||||
websocket.accept(self.request, self.headers)
|
||||
except Exception:
|
||||
exc = sys.exc_info()[1]
|
||||
self.send_error(400, str(exc))
|
||||
return
|
||||
|
||||
self.log_request(101)
|
||||
|
||||
self.request = websocket
|
||||
|
||||
# Other requests cannot follow Websocket data
|
||||
self.close_connection = True
|
||||
|
||||
self.handle_websocket()
|
||||
|
||||
def handle_websocket(self):
|
||||
"""Handle a WebSocket connection.
|
||||
|
||||
This is called when the WebSocket is ready to be used. A
|
||||
sub-class should perform the necessary communication here and
|
||||
return once done.
|
||||
"""
|
||||
pass
|
||||
|
||||
class WebSocketServer(HTTPServer):
|
||||
pass
|
||||
@@ -0,0 +1,797 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Python WebSocket server base with support for "wss://" encryption.
|
||||
Copyright 2011 Joel Martin
|
||||
Copyright 2016 Pierre Ossman
|
||||
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
|
||||
You can make a cert/key with openssl using:
|
||||
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
||||
as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||
|
||||
'''
|
||||
|
||||
import os, sys, time, errno, signal, socket, select, logging
|
||||
import multiprocessing
|
||||
|
||||
# Imports that vary by python version
|
||||
|
||||
# python 3.0 differences
|
||||
if sys.hexversion > 0x3000000:
|
||||
s2b = lambda s: s.encode('latin_1')
|
||||
else:
|
||||
s2b = lambda s: s # No-op
|
||||
try: from http.server import SimpleHTTPRequestHandler
|
||||
except: from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
|
||||
# Degraded functionality if these imports are missing
|
||||
for mod, msg in [('ssl', 'TLS/SSL/wss is disabled'),
|
||||
('resource', 'daemonizing is disabled')]:
|
||||
try:
|
||||
globals()[mod] = __import__(mod)
|
||||
except ImportError:
|
||||
globals()[mod] = None
|
||||
print("WARNING: no '%s' module, %s" % (mod, msg))
|
||||
|
||||
if sys.platform == 'win32':
|
||||
# make sockets pickle-able/inheritable
|
||||
import multiprocessing.reduction
|
||||
|
||||
from websockify.websocket import WebSocket, WebSocketWantReadError, WebSocketWantWriteError
|
||||
from websockify.websocketserver import WebSocketRequestHandler
|
||||
|
||||
class CompatibleWebSocket(WebSocket):
|
||||
def select_subprotocol(self, protocols):
|
||||
# Handle old websockify clients that still specifiy a sub-protocol
|
||||
if 'binary' in protocols:
|
||||
return 'binary'
|
||||
else:
|
||||
return ''
|
||||
|
||||
# HTTP handler with WebSocket upgrade support
|
||||
class WebSockifyRequestHandler(WebSocketRequestHandler, SimpleHTTPRequestHandler):
|
||||
"""
|
||||
WebSocket Request Handler Class, derived from SimpleHTTPRequestHandler.
|
||||
Must be sub-classed with new_websocket_client method definition.
|
||||
The request handler can be configured by setting optional
|
||||
attributes on the server object:
|
||||
|
||||
* only_upgrade: If true, SimpleHTTPRequestHandler will not be enabled,
|
||||
only websocket is allowed.
|
||||
* verbose: If true, verbose logging is activated.
|
||||
* daemon: Running as daemon, do not write to console etc
|
||||
* record: Record raw frame data as JavaScript array into specified filename
|
||||
* run_once: Handle a single request
|
||||
* handler_id: A sequence number for this connection, appended to record filename
|
||||
"""
|
||||
server_version = "WebSockify"
|
||||
|
||||
protocol_version = "HTTP/1.1"
|
||||
|
||||
SocketClass = CompatibleWebSocket
|
||||
|
||||
# An exception while the WebSocket client was connected
|
||||
class CClose(Exception):
|
||||
pass
|
||||
|
||||
def __init__(self, req, addr, server):
|
||||
# Retrieve a few configuration variables from the server
|
||||
self.only_upgrade = getattr(server, "only_upgrade", False)
|
||||
self.verbose = getattr(server, "verbose", False)
|
||||
self.daemon = getattr(server, "daemon", False)
|
||||
self.record = getattr(server, "record", False)
|
||||
self.run_once = getattr(server, "run_once", False)
|
||||
self.rec = None
|
||||
self.handler_id = getattr(server, "handler_id", False)
|
||||
self.file_only = getattr(server, "file_only", False)
|
||||
self.traffic = getattr(server, "traffic", False)
|
||||
|
||||
self.logger = getattr(server, "logger", None)
|
||||
if self.logger is None:
|
||||
self.logger = WebSockifyServer.get_logger()
|
||||
|
||||
WebSocketRequestHandler.__init__(self, req, addr, server)
|
||||
|
||||
def log_message(self, format, *args):
|
||||
self.logger.info("%s - - [%s] %s" % (self.address_string(), self.log_date_time_string(), format % args))
|
||||
|
||||
#
|
||||
# WebSocketRequestHandler logging/output functions
|
||||
#
|
||||
|
||||
def print_traffic(self, token="."):
|
||||
""" Show traffic flow mode. """
|
||||
if self.traffic:
|
||||
sys.stdout.write(token)
|
||||
sys.stdout.flush()
|
||||
|
||||
def msg(self, msg, *args, **kwargs):
|
||||
""" Output message with handler_id prefix. """
|
||||
prefix = "% 3d: " % self.handler_id
|
||||
self.logger.log(logging.INFO, "%s%s" % (prefix, msg), *args, **kwargs)
|
||||
|
||||
def vmsg(self, msg, *args, **kwargs):
|
||||
""" Same as msg() but as debug. """
|
||||
prefix = "% 3d: " % self.handler_id
|
||||
self.logger.log(logging.DEBUG, "%s%s" % (prefix, msg), *args, **kwargs)
|
||||
|
||||
def warn(self, msg, *args, **kwargs):
|
||||
""" Same as msg() but as warning. """
|
||||
prefix = "% 3d: " % self.handler_id
|
||||
self.logger.log(logging.WARN, "%s%s" % (prefix, msg), *args, **kwargs)
|
||||
|
||||
#
|
||||
# Main WebSocketRequestHandler methods
|
||||
#
|
||||
def send_frames(self, bufs=None):
|
||||
""" Encode and send WebSocket frames. Any frames already
|
||||
queued will be sent first. If buf is not set then only queued
|
||||
frames will be sent. Returns True if any frames could not be
|
||||
fully sent, in which case the caller should call again when
|
||||
the socket is ready. """
|
||||
|
||||
tdelta = int(time.time()*1000) - self.start_time
|
||||
|
||||
if bufs:
|
||||
for buf in bufs:
|
||||
if self.rec:
|
||||
self.rec.write("%s,\n" % repr("{%s{" % tdelta + buf))
|
||||
self.send_parts.append(buf)
|
||||
|
||||
# Flush any previously queued data
|
||||
try:
|
||||
self.request.sendmsg('')
|
||||
except WebSocketWantWriteError:
|
||||
return True
|
||||
|
||||
while self.send_parts:
|
||||
# Send pending frames
|
||||
buf = self.send_parts.pop(0)
|
||||
try:
|
||||
self.request.sendmsg(buf)
|
||||
except WebSocketWantWriteError:
|
||||
self.print_traffic("<.")
|
||||
return True
|
||||
self.print_traffic("<")
|
||||
|
||||
return False
|
||||
|
||||
def recv_frames(self):
|
||||
""" Receive and decode WebSocket frames.
|
||||
|
||||
Returns:
|
||||
(bufs_list, closed_string)
|
||||
"""
|
||||
|
||||
closed = False
|
||||
bufs = []
|
||||
tdelta = int(time.time()*1000) - self.start_time
|
||||
|
||||
while True:
|
||||
try:
|
||||
buf = self.request.recvmsg()
|
||||
except WebSocketWantReadError:
|
||||
self.print_traffic("}.")
|
||||
break
|
||||
|
||||
if len(buf) == 0:
|
||||
closed = {'code': self.request.close_code,
|
||||
'reason': self.request.close_reason}
|
||||
return bufs, closed
|
||||
|
||||
self.print_traffic("}")
|
||||
|
||||
if self.rec:
|
||||
self.rec.write("%s,\n" % repr("}%s}" % tdelta + buf))
|
||||
|
||||
bufs.append(buf)
|
||||
|
||||
if not self.request.pending():
|
||||
break
|
||||
|
||||
return bufs, closed
|
||||
|
||||
def send_close(self, code=1000, reason=''):
|
||||
""" Send a WebSocket orderly close frame. """
|
||||
self.request.shutdown(socket.SHUT_RDWR, code, reason)
|
||||
|
||||
def send_pong(self, data=''):
|
||||
""" Send a WebSocket pong frame. """
|
||||
self.request.pong(data)
|
||||
|
||||
def send_ping(self, data=''):
|
||||
""" Send a WebSocket ping frame. """
|
||||
self.request.ping(data)
|
||||
|
||||
def handle_upgrade(self):
|
||||
# ensure connection is authorized, and determine the target
|
||||
self.validate_connection()
|
||||
|
||||
WebSocketRequestHandler.handle_upgrade(self)
|
||||
|
||||
def handle_websocket(self):
|
||||
# Indicate to server that a Websocket upgrade was done
|
||||
self.server.ws_connection = True
|
||||
# Initialize per client settings
|
||||
self.send_parts = []
|
||||
self.recv_part = None
|
||||
self.start_time = int(time.time()*1000)
|
||||
|
||||
# client_address is empty with, say, UNIX domain sockets
|
||||
client_addr = ""
|
||||
is_ssl = False
|
||||
try:
|
||||
client_addr = self.client_address[0]
|
||||
is_ssl = self.client_address[2]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if is_ssl:
|
||||
self.stype = "SSL/TLS (wss://)"
|
||||
else:
|
||||
self.stype = "Plain non-SSL (ws://)"
|
||||
|
||||
self.log_message("%s: %s WebSocket connection", client_addr,
|
||||
self.stype)
|
||||
if self.path != '/':
|
||||
self.log_message("%s: Path: '%s'", client_addr, self.path)
|
||||
|
||||
if self.record:
|
||||
# Record raw frame data as JavaScript array
|
||||
fname = "%s.%s" % (self.record,
|
||||
self.handler_id)
|
||||
self.log_message("opening record file: %s", fname)
|
||||
self.rec = open(fname, 'w+')
|
||||
self.rec.write("var VNC_frame_data = [\n")
|
||||
|
||||
try:
|
||||
self.new_websocket_client()
|
||||
except self.CClose:
|
||||
# Close the client
|
||||
_, exc, _ = sys.exc_info()
|
||||
self.send_close(exc.args[0], exc.args[1])
|
||||
|
||||
def do_GET(self):
|
||||
if self.only_upgrade:
|
||||
self.send_error(405, "Method Not Allowed")
|
||||
else:
|
||||
SimpleHTTPRequestHandler.do_GET(self)
|
||||
|
||||
def list_directory(self, path):
|
||||
if self.file_only:
|
||||
self.send_error(404, "No such file")
|
||||
else:
|
||||
return SimpleHTTPRequestHandler.list_directory(self, path)
|
||||
|
||||
def new_websocket_client(self):
|
||||
""" Do something with a WebSockets client connection. """
|
||||
raise Exception("WebSocketRequestHandler.new_websocket_client() must be overloaded")
|
||||
|
||||
def validate_connection(self):
|
||||
""" Ensure that the connection is a valid connection, and set the target. """
|
||||
pass
|
||||
|
||||
def do_HEAD(self):
|
||||
if self.only_upgrade:
|
||||
self.send_error(405, "Method Not Allowed")
|
||||
else:
|
||||
SimpleHTTPRequestHandler.do_HEAD(self)
|
||||
|
||||
def finish(self):
|
||||
if self.rec:
|
||||
self.rec.write("'EOF'];\n")
|
||||
self.rec.close()
|
||||
|
||||
def handle(self):
|
||||
# When using run_once, we have a single process, so
|
||||
# we cannot loop in BaseHTTPRequestHandler.handle; we
|
||||
# must return and handle new connections
|
||||
if self.run_once:
|
||||
self.handle_one_request()
|
||||
else:
|
||||
SimpleHTTPRequestHandler.handle(self)
|
||||
|
||||
def log_request(self, code='-', size='-'):
|
||||
if self.verbose:
|
||||
SimpleHTTPRequestHandler.log_request(self, code, size)
|
||||
|
||||
|
||||
class WebSockifyServer(object):
|
||||
"""
|
||||
WebSockets server class.
|
||||
As an alternative, the standard library SocketServer can be used
|
||||
"""
|
||||
|
||||
policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
|
||||
log_prefix = "websocket"
|
||||
|
||||
# An exception before the WebSocket connection was established
|
||||
class EClose(Exception):
|
||||
pass
|
||||
|
||||
class Terminate(Exception):
|
||||
pass
|
||||
|
||||
def __init__(self, RequestHandlerClass, listen_host='',
|
||||
listen_port=None, source_is_ipv6=False,
|
||||
verbose=False, cert='', key='', ssl_only=None,
|
||||
daemon=False, record='', web='',
|
||||
file_only=False,
|
||||
run_once=False, timeout=0, idle_timeout=0, traffic=False,
|
||||
tcp_keepalive=True, tcp_keepcnt=None, tcp_keepidle=None,
|
||||
tcp_keepintvl=None):
|
||||
|
||||
# settings
|
||||
self.RequestHandlerClass = RequestHandlerClass
|
||||
self.verbose = verbose
|
||||
self.listen_host = listen_host
|
||||
self.listen_port = listen_port
|
||||
self.prefer_ipv6 = source_is_ipv6
|
||||
self.ssl_only = ssl_only
|
||||
self.daemon = daemon
|
||||
self.run_once = run_once
|
||||
self.timeout = timeout
|
||||
self.idle_timeout = idle_timeout
|
||||
self.traffic = traffic
|
||||
self.file_only = file_only
|
||||
|
||||
self.launch_time = time.time()
|
||||
self.ws_connection = False
|
||||
self.handler_id = 1
|
||||
|
||||
self.logger = self.get_logger()
|
||||
self.tcp_keepalive = tcp_keepalive
|
||||
self.tcp_keepcnt = tcp_keepcnt
|
||||
self.tcp_keepidle = tcp_keepidle
|
||||
self.tcp_keepintvl = tcp_keepintvl
|
||||
|
||||
# Make paths settings absolute
|
||||
self.cert = os.path.abspath(cert)
|
||||
self.key = self.web = self.record = ''
|
||||
if key:
|
||||
self.key = os.path.abspath(key)
|
||||
if web:
|
||||
self.web = os.path.abspath(web)
|
||||
if record:
|
||||
self.record = os.path.abspath(record)
|
||||
|
||||
if self.web:
|
||||
os.chdir(self.web)
|
||||
self.only_upgrade = not self.web
|
||||
|
||||
# Sanity checks
|
||||
if not ssl and self.ssl_only:
|
||||
raise Exception("No 'ssl' module and SSL-only specified")
|
||||
if self.daemon and not resource:
|
||||
raise Exception("Module 'resource' required to daemonize")
|
||||
|
||||
# Show configuration
|
||||
self.msg("WebSocket server settings:")
|
||||
self.msg(" - Listen on %s:%s",
|
||||
self.listen_host, self.listen_port)
|
||||
if self.web:
|
||||
if self.file_only:
|
||||
self.msg(" - Web server (no directory listings). Web root: %s", self.web)
|
||||
else:
|
||||
self.msg(" - Web server. Web root: %s", self.web)
|
||||
if ssl:
|
||||
if os.path.exists(self.cert):
|
||||
self.msg(" - SSL/TLS support")
|
||||
if self.ssl_only:
|
||||
self.msg(" - Deny non-SSL/TLS connections")
|
||||
else:
|
||||
self.msg(" - No SSL/TLS support (no cert file)")
|
||||
else:
|
||||
self.msg(" - No SSL/TLS support (no 'ssl' module)")
|
||||
if self.daemon:
|
||||
self.msg(" - Backgrounding (daemon)")
|
||||
if self.record:
|
||||
self.msg(" - Recording to '%s.*'", self.record)
|
||||
|
||||
#
|
||||
# WebSockifyServer static methods
|
||||
#
|
||||
|
||||
@staticmethod
|
||||
def get_logger():
|
||||
return logging.getLogger("%s.%s" % (
|
||||
WebSockifyServer.log_prefix,
|
||||
WebSockifyServer.__class__.__name__))
|
||||
|
||||
@staticmethod
|
||||
def socket(host, port=None, connect=False, prefer_ipv6=False,
|
||||
unix_socket=None, use_ssl=False, tcp_keepalive=True,
|
||||
tcp_keepcnt=None, tcp_keepidle=None, tcp_keepintvl=None):
|
||||
""" Resolve a host (and optional port) to an IPv4 or IPv6
|
||||
address. Create a socket. Bind to it if listen is set,
|
||||
otherwise connect to it. Return the socket.
|
||||
"""
|
||||
flags = 0
|
||||
if host == '':
|
||||
host = None
|
||||
if connect and not (port or unix_socket):
|
||||
raise Exception("Connect mode requires a port")
|
||||
if use_ssl and not ssl:
|
||||
raise Exception("SSL socket requested but Python SSL module not loaded.");
|
||||
if not connect and use_ssl:
|
||||
raise Exception("SSL only supported in connect mode (for now)")
|
||||
if not connect:
|
||||
flags = flags | socket.AI_PASSIVE
|
||||
|
||||
if not unix_socket:
|
||||
addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
|
||||
socket.IPPROTO_TCP, flags)
|
||||
if not addrs:
|
||||
raise Exception("Could not resolve host '%s'" % host)
|
||||
addrs.sort(key=lambda x: x[0])
|
||||
if prefer_ipv6:
|
||||
addrs.reverse()
|
||||
sock = socket.socket(addrs[0][0], addrs[0][1])
|
||||
|
||||
if tcp_keepalive:
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
if tcp_keepcnt:
|
||||
sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT,
|
||||
tcp_keepcnt)
|
||||
if tcp_keepidle:
|
||||
sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE,
|
||||
tcp_keepidle)
|
||||
if tcp_keepintvl:
|
||||
sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL,
|
||||
tcp_keepintvl)
|
||||
|
||||
if connect:
|
||||
sock.connect(addrs[0][4])
|
||||
if use_ssl:
|
||||
sock = ssl.wrap_socket(sock)
|
||||
else:
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind(addrs[0][4])
|
||||
sock.listen(100)
|
||||
else:
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(unix_socket)
|
||||
|
||||
return sock
|
||||
|
||||
@staticmethod
|
||||
def daemonize(keepfd=None, chdir='/'):
|
||||
|
||||
if keepfd is None:
|
||||
keepfd = []
|
||||
|
||||
os.umask(0)
|
||||
if chdir:
|
||||
os.chdir(chdir)
|
||||
else:
|
||||
os.chdir('/')
|
||||
os.setgid(os.getgid()) # relinquish elevations
|
||||
os.setuid(os.getuid()) # relinquish elevations
|
||||
|
||||
# Double fork to daemonize
|
||||
if os.fork() > 0: os._exit(0) # Parent exits
|
||||
os.setsid() # Obtain new process group
|
||||
if os.fork() > 0: os._exit(0) # Parent exits
|
||||
|
||||
# Signal handling
|
||||
signal.signal(signal.SIGTERM, signal.SIG_IGN)
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
|
||||
# Close open files
|
||||
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
|
||||
if maxfd == resource.RLIM_INFINITY: maxfd = 256
|
||||
for fd in reversed(range(maxfd)):
|
||||
try:
|
||||
if fd not in keepfd:
|
||||
os.close(fd)
|
||||
except OSError:
|
||||
_, exc, _ = sys.exc_info()
|
||||
if exc.errno != errno.EBADF: raise
|
||||
|
||||
# Redirect I/O to /dev/null
|
||||
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno())
|
||||
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno())
|
||||
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno())
|
||||
|
||||
def do_handshake(self, sock, address):
|
||||
"""
|
||||
do_handshake does the following:
|
||||
- Peek at the first few bytes from the socket.
|
||||
- If the connection is an HTTPS/SSL/TLS connection then SSL
|
||||
wrap the socket.
|
||||
- Read from the (possibly wrapped) socket.
|
||||
- If we have received a HTTP GET request and the webserver
|
||||
functionality is enabled, answer it, close the socket and
|
||||
return.
|
||||
- Assume we have a WebSockets connection, parse the client
|
||||
handshake data.
|
||||
- Send a WebSockets handshake server response.
|
||||
- Return the socket for this WebSocket client.
|
||||
"""
|
||||
ready = select.select([sock], [], [], 3)[0]
|
||||
|
||||
|
||||
if not ready:
|
||||
raise self.EClose("ignoring socket not ready")
|
||||
# Peek, but do not read the data so that we have a opportunity
|
||||
# to SSL wrap the socket first
|
||||
handshake = sock.recv(1024, socket.MSG_PEEK)
|
||||
#self.msg("Handshake [%s]" % handshake)
|
||||
|
||||
if not handshake:
|
||||
raise self.EClose("ignoring empty handshake")
|
||||
|
||||
elif handshake[0] in ("\x16", "\x80", 22, 128):
|
||||
# SSL wrap the connection
|
||||
if not ssl:
|
||||
raise self.EClose("SSL connection but no 'ssl' module")
|
||||
if not os.path.exists(self.cert):
|
||||
raise self.EClose("SSL connection but '%s' not found"
|
||||
% self.cert)
|
||||
retsock = None
|
||||
try:
|
||||
retsock = ssl.wrap_socket(
|
||||
sock,
|
||||
server_side=True,
|
||||
certfile=self.cert,
|
||||
keyfile=self.key)
|
||||
except ssl.SSLError:
|
||||
_, x, _ = sys.exc_info()
|
||||
if x.args[0] == ssl.SSL_ERROR_EOF:
|
||||
if len(x.args) > 1:
|
||||
raise self.EClose(x.args[1])
|
||||
else:
|
||||
raise self.EClose("Got SSL_ERROR_EOF")
|
||||
else:
|
||||
raise
|
||||
|
||||
elif self.ssl_only:
|
||||
raise self.EClose("non-SSL connection received but disallowed")
|
||||
|
||||
else:
|
||||
retsock = sock
|
||||
|
||||
# If the address is like (host, port), we are extending it
|
||||
# with a flag indicating SSL. Not many other options
|
||||
# available...
|
||||
if len(address) == 2:
|
||||
address = (address[0], address[1], (retsock != sock))
|
||||
|
||||
self.RequestHandlerClass(retsock, address, self)
|
||||
|
||||
# Return the WebSockets socket which may be SSL wrapped
|
||||
return retsock
|
||||
|
||||
#
|
||||
# WebSockifyServer logging/output functions
|
||||
#
|
||||
|
||||
def msg(self, *args, **kwargs):
|
||||
""" Output message as info """
|
||||
self.logger.log(logging.INFO, *args, **kwargs)
|
||||
|
||||
def vmsg(self, *args, **kwargs):
|
||||
""" Same as msg() but as debug. """
|
||||
self.logger.log(logging.DEBUG, *args, **kwargs)
|
||||
|
||||
def warn(self, *args, **kwargs):
|
||||
""" Same as msg() but as warning. """
|
||||
self.logger.log(logging.WARN, *args, **kwargs)
|
||||
|
||||
|
||||
#
|
||||
# Events that can/should be overridden in sub-classes
|
||||
#
|
||||
def started(self):
|
||||
""" Called after WebSockets startup """
|
||||
self.vmsg("WebSockets server started")
|
||||
|
||||
def poll(self):
|
||||
""" Run periodically while waiting for connections. """
|
||||
#self.vmsg("Running poll()")
|
||||
pass
|
||||
|
||||
def terminate(self):
|
||||
raise self.Terminate()
|
||||
|
||||
def multiprocessing_SIGCHLD(self, sig, stack):
|
||||
# TODO: figure out a way to actually log this information without
|
||||
# calling `log` in the signal handlers
|
||||
multiprocessing.active_children()
|
||||
|
||||
def fallback_SIGCHLD(self, sig, stack):
|
||||
# Reap zombies when using os.fork() (python 2.4)
|
||||
# TODO: figure out a way to actually log this information without
|
||||
# calling `log` in the signal handlers
|
||||
try:
|
||||
result = os.waitpid(-1, os.WNOHANG)
|
||||
while result[0]:
|
||||
self.vmsg("Reaped child process %s" % result[0])
|
||||
result = os.waitpid(-1, os.WNOHANG)
|
||||
except (OSError):
|
||||
pass
|
||||
|
||||
def do_SIGINT(self, sig, stack):
|
||||
# TODO: figure out a way to actually log this information without
|
||||
# calling `log` in the signal handlers
|
||||
self.terminate()
|
||||
|
||||
def do_SIGTERM(self, sig, stack):
|
||||
# TODO: figure out a way to actually log this information without
|
||||
# calling `log` in the signal handlers
|
||||
self.terminate()
|
||||
|
||||
def top_new_client(self, startsock, address):
|
||||
""" Do something with a WebSockets client connection. """
|
||||
# handler process
|
||||
client = None
|
||||
try:
|
||||
try:
|
||||
client = self.do_handshake(startsock, address)
|
||||
except self.EClose:
|
||||
_, exc, _ = sys.exc_info()
|
||||
# Connection was not a WebSockets connection
|
||||
if exc.args[0]:
|
||||
self.msg("%s: %s" % (address[0], exc.args[0]))
|
||||
except WebSockifyServer.Terminate:
|
||||
raise
|
||||
except Exception:
|
||||
_, exc, _ = sys.exc_info()
|
||||
self.msg("handler exception: %s" % str(exc))
|
||||
self.vmsg("exception", exc_info=True)
|
||||
finally:
|
||||
|
||||
if client and client != startsock:
|
||||
# Close the SSL wrapped socket
|
||||
# Original socket closed by caller
|
||||
client.close()
|
||||
|
||||
def get_log_fd(self):
|
||||
"""
|
||||
Get file descriptors for the loggers.
|
||||
They should not be closed when the process is forked.
|
||||
"""
|
||||
descriptors = []
|
||||
for handler in self.logger.parent.handlers:
|
||||
if isinstance(handler, logging.FileHandler):
|
||||
descriptors.append(handler.stream.fileno())
|
||||
|
||||
return descriptors
|
||||
|
||||
def start_server(self):
|
||||
"""
|
||||
Daemonize if requested. Listen for for connections. Run
|
||||
do_handshake() method for each connection. If the connection
|
||||
is a WebSockets client then call new_websocket_client() method (which must
|
||||
be overridden) for each new client connection.
|
||||
"""
|
||||
lsock = self.socket(self.listen_host, self.listen_port, False,
|
||||
self.prefer_ipv6,
|
||||
tcp_keepalive=self.tcp_keepalive,
|
||||
tcp_keepcnt=self.tcp_keepcnt,
|
||||
tcp_keepidle=self.tcp_keepidle,
|
||||
tcp_keepintvl=self.tcp_keepintvl)
|
||||
|
||||
if self.daemon:
|
||||
keepfd = self.get_log_fd()
|
||||
keepfd.append(lsock.fileno())
|
||||
self.daemonize(keepfd=keepfd, chdir=self.web)
|
||||
|
||||
self.started() # Some things need to happen after daemonizing
|
||||
|
||||
# Allow override of signals
|
||||
original_signals = {
|
||||
signal.SIGINT: signal.getsignal(signal.SIGINT),
|
||||
signal.SIGTERM: signal.getsignal(signal.SIGTERM),
|
||||
signal.SIGCHLD: signal.getsignal(signal.SIGCHLD),
|
||||
}
|
||||
signal.signal(signal.SIGINT, self.do_SIGINT)
|
||||
signal.signal(signal.SIGTERM, self.do_SIGTERM)
|
||||
# make sure that _cleanup is called when children die
|
||||
# by calling active_children on SIGCHLD
|
||||
signal.signal(signal.SIGCHLD, self.multiprocessing_SIGCHLD)
|
||||
|
||||
last_active_time = self.launch_time
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
try:
|
||||
startsock = None
|
||||
pid = err = 0
|
||||
child_count = 0
|
||||
|
||||
# Collect zombie child processes
|
||||
child_count = len(multiprocessing.active_children())
|
||||
|
||||
time_elapsed = time.time() - self.launch_time
|
||||
if self.timeout and time_elapsed > self.timeout:
|
||||
self.msg('listener exit due to --timeout %s'
|
||||
% self.timeout)
|
||||
break
|
||||
|
||||
if self.idle_timeout:
|
||||
idle_time = 0
|
||||
if child_count == 0:
|
||||
idle_time = time.time() - last_active_time
|
||||
else:
|
||||
idle_time = 0
|
||||
last_active_time = time.time()
|
||||
|
||||
if idle_time > self.idle_timeout and child_count == 0:
|
||||
self.msg('listener exit due to --idle-timeout %s'
|
||||
% self.idle_timeout)
|
||||
break
|
||||
|
||||
try:
|
||||
self.poll()
|
||||
|
||||
ready = select.select([lsock], [], [], 1)[0]
|
||||
if lsock in ready:
|
||||
startsock, address = lsock.accept()
|
||||
else:
|
||||
continue
|
||||
except self.Terminate:
|
||||
raise
|
||||
except Exception:
|
||||
_, exc, _ = sys.exc_info()
|
||||
if hasattr(exc, 'errno'):
|
||||
err = exc.errno
|
||||
elif hasattr(exc, 'args'):
|
||||
err = exc.args[0]
|
||||
else:
|
||||
err = exc[0]
|
||||
if err == errno.EINTR:
|
||||
self.vmsg("Ignoring interrupted syscall")
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
|
||||
if self.run_once:
|
||||
# Run in same process if run_once
|
||||
self.top_new_client(startsock, address)
|
||||
if self.ws_connection :
|
||||
self.msg('%s: exiting due to --run-once'
|
||||
% address[0])
|
||||
break
|
||||
else:
|
||||
self.vmsg('%s: new handler Process' % address[0])
|
||||
p = multiprocessing.Process(
|
||||
target=self.top_new_client,
|
||||
args=(startsock, address))
|
||||
p.start()
|
||||
# child will not return
|
||||
|
||||
# parent process
|
||||
self.handler_id += 1
|
||||
|
||||
except (self.Terminate, SystemExit, KeyboardInterrupt):
|
||||
self.msg("In exit")
|
||||
# terminate all child processes
|
||||
if not self.run_once:
|
||||
children = multiprocessing.active_children()
|
||||
|
||||
for child in children:
|
||||
self.msg("Terminating child %s" % child.pid)
|
||||
child.terminate()
|
||||
|
||||
break
|
||||
except Exception:
|
||||
exc = sys.exc_info()[1]
|
||||
self.msg("handler exception: %s", str(exc))
|
||||
self.vmsg("exception", exc_info=True)
|
||||
|
||||
finally:
|
||||
if startsock:
|
||||
startsock.close()
|
||||
finally:
|
||||
# Close listen port
|
||||
self.vmsg("Closing socket listening at %s:%s",
|
||||
self.listen_host, self.listen_port)
|
||||
lsock.close()
|
||||
|
||||
# Restore signals
|
||||
for sig, func in original_signals.items():
|
||||
signal.signal(sig, func)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
<head>
|
||||
<title>IRC Client using WebSockets</title>
|
||||
<script src="include/base64.js"></script>
|
||||
<script src="include/websock.js"></script>
|
||||
<script src="include/util.js"></script>
|
||||
<script src="include/webutil.js"></script>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
<head>
|
||||
<title>Telnet client using WebSockets</title>
|
||||
<script src="include/base64.js"></script>
|
||||
<script src="include/util.js"></script>
|
||||
<script src="include/websock.js"></script>
|
||||
<script src="include/webutil.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user