Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 69c04c819c | |||
| 558a6439f1 | |||
| 2b9e0a5794 | |||
| 3371090909 | |||
| 1f960d9f3c | |||
| 1221960baa | |||
| 2e57ee3159 | |||
| 04fd789a67 | |||
| d5216c3166 | |||
| 11f9a6cf10 | |||
| df10501615 | |||
| d94b944f93 | |||
| 52adf957b3 | |||
| 731fd20796 | |||
| 52c2e62535 | |||
| ac9d357c87 | |||
| ce07749223 | |||
| af10f458a1 | |||
| 69a8b928aa | |||
| 19b558e4cd | |||
| dcdf0a0b6c | |||
| 23045cb212 | |||
| acd276e1a2 | |||
| 999a133bc5 | |||
| f36877c684 | |||
| 303a71310c | |||
| 63209f84ca | |||
| d37e729587 | |||
| 12191d266b | |||
| 67a12a2394 | |||
| 6c526fd645 | |||
| 6c1a2e9032 |
@@ -9,3 +9,5 @@ other/node_modules
|
||||
.pydevproject
|
||||
target.cfg
|
||||
target.cfg.d
|
||||
.tox
|
||||
*.egg-info
|
||||
|
||||
+10
@@ -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
|
||||
+12
@@ -1,6 +1,18 @@
|
||||
Changes
|
||||
=======
|
||||
|
||||
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
|
||||
--------------------
|
||||
|
||||
|
||||
+9
-7
@@ -262,7 +262,7 @@ function on(evt, handler) {
|
||||
eventHandlers[evt] = handler;
|
||||
}
|
||||
|
||||
function init(protocols) {
|
||||
function init(protocols, ws_schema) {
|
||||
rQ = [];
|
||||
rQi = 0;
|
||||
sQ = [];
|
||||
@@ -277,12 +277,13 @@ function init(protocols) {
|
||||
('set' in Uint8Array.prototype)) {
|
||||
bt = true;
|
||||
}
|
||||
|
||||
// Check for full binary type support in WebSockets
|
||||
// TODO: this sucks, the property should exist on the prototype
|
||||
// but it does not.
|
||||
// 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 (new WebSocket("ws://localhost:17523")))) {
|
||||
if (bt && ('binaryType' in WebSocket.prototype ||
|
||||
!!(new WebSocket(ws_schema + '://.').binaryType))) {
|
||||
Util.Info("Detected binaryType support in WebSockets");
|
||||
wsbt = true;
|
||||
}
|
||||
@@ -325,7 +326,8 @@ function init(protocols) {
|
||||
}
|
||||
|
||||
function open(uri, protocols) {
|
||||
protocols = init(protocols);
|
||||
var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
|
||||
protocols = init(protocols, ws_schema);
|
||||
|
||||
if (test_mode) {
|
||||
websocket = {};
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
"name": "websockify",
|
||||
"description": "websockify is a WebSocket-to-TCP proxy/bridge",
|
||||
"license": "LGPL-3",
|
||||
"version": "0.6.1",
|
||||
"version": "0.7.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/kanaka/websockify.git"
|
||||
},
|
||||
"files": ["../../docs/LICENSE.LGPL-3"],
|
||||
"files": ["../../docs/LICENSE.LGPL-3","websockify.js"],
|
||||
"bin": {
|
||||
"websockify": "./websockify.js"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
version = '0.6.1'
|
||||
version = '0.7.0'
|
||||
name = 'websockify'
|
||||
long_description = open("README.md").read() + "\n" + \
|
||||
open("CHANGES.txt").read() + "\n"
|
||||
@@ -11,6 +11,12 @@ setup(name=name,
|
||||
long_description=long_description,
|
||||
classifiers=[
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 2",
|
||||
"Programming Language :: Python :: 2.6",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.3",
|
||||
"Programming Language :: Python :: 3.4"
|
||||
],
|
||||
data_files=[('share/websockify/include',
|
||||
['include/util.js',
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
mox
|
||||
nose
|
||||
+338
-151
@@ -26,201 +26,303 @@ import stubout
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from ssl import SSLError
|
||||
from websockify import websocket as websocket
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
import socket
|
||||
import signal
|
||||
from websockify import websocket
|
||||
|
||||
try:
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
except ImportError:
|
||||
from http.server import SimpleHTTPRequestHandler
|
||||
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
BytesIO = StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
class MockConnection(object):
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
def makefile(self, mode='r', bufsize=-1):
|
||||
return open(self.path, mode, bufsize)
|
||||
|
||||
|
||||
class WebSocketTestCase(unittest.TestCase):
|
||||
def raise_oserror(*args, **kwargs):
|
||||
raise OSError('fake error')
|
||||
|
||||
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)
|
||||
|
||||
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 WebSocketRequestHandlerTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Called automatically before each test."""
|
||||
super(WebSocketTestCase, self).setUp()
|
||||
super(WebSocketRequestHandlerTestCase, self).setUp()
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
# Temporary dir for test data
|
||||
self.tmpdir = tempfile.mkdtemp()
|
||||
# Put log somewhere persistent
|
||||
self._init_logger('./')
|
||||
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
|
||||
# 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')
|
||||
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
|
||||
lambda *args, **kwargs: None)
|
||||
|
||||
def tearDown(self):
|
||||
"""Called automatically after each test."""
|
||||
self.stubs.UnsetAll()
|
||||
shutil.rmtree(self.tmpdir)
|
||||
super(WebSocketTestCase, self).tearDown()
|
||||
os.rmdir(self.tmpdir)
|
||||
super(WebSocketRequestHandlerTestCase, self).tearDown()
|
||||
|
||||
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 _get_server(self, handler_class=websocket.WebSocketRequestHandler,
|
||||
**kwargs):
|
||||
web = kwargs.pop('web', self.tmpdir)
|
||||
return websocket.WebSocketServer(
|
||||
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 _mock_os_open_oserror(self, file, flags):
|
||||
raise OSError('')
|
||||
def test_normal_get_with_only_upgrade_returns_error(self):
|
||||
server = self._get_server(web=None)
|
||||
handler = websocket.WebSocketRequestHandler(
|
||||
FakeSocket('GET /tmp.txt HTTP/1.1'), '127.0.0.1', server)
|
||||
|
||||
def _mock_os_close_oserror(self, fd):
|
||||
raise OSError('')
|
||||
def fake_send_response(self, code, message=None):
|
||||
self.last_code = code
|
||||
|
||||
def _mock_os_close_oserror_EBADF(self, fd):
|
||||
raise OSError(errno.EBADF, '')
|
||||
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
|
||||
fake_send_response)
|
||||
|
||||
def _mock_socket(self, *args, **kwargs):
|
||||
return self.soc
|
||||
handler.do_GET()
|
||||
self.assertEqual(handler.last_code, 405)
|
||||
|
||||
def _mock_select(self, rlist, wlist, xlist, timeout=None):
|
||||
return '_mock_select'
|
||||
def test_list_dir_with_file_only_returns_error(self):
|
||||
server = self._get_server(file_only=True)
|
||||
handler = websocket.WebSocketRequestHandler(
|
||||
FakeSocket('GET / HTTP/1.1'), '127.0.0.1', server)
|
||||
|
||||
def _mock_select_exception(self, rlist, wlist, xlist, timeout=None):
|
||||
raise Exception
|
||||
def fake_send_response(self, code, message=None):
|
||||
self.last_code = code
|
||||
|
||||
def _mock_select_keyboardinterrupt(self, rlist, wlist,
|
||||
xlist, timeout=None):
|
||||
raise KeyboardInterrupt
|
||||
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
|
||||
fake_send_response)
|
||||
|
||||
def _mock_select_systemexit(self, rlist, wlist, xlist, timeout=None):
|
||||
sys.exit()
|
||||
handler.path = '/'
|
||||
handler.do_GET()
|
||||
self.assertEqual(handler.last_code, 404)
|
||||
|
||||
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)
|
||||
|
||||
class WebSocketServerTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(WebSocketServerTestCase, 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(WebSocketServerTestCase, self).tearDown()
|
||||
|
||||
def _get_server(self, handler_class=websocket.WebSocketRequestHandler,
|
||||
**kwargs):
|
||||
return websocket.WebSocketServer(
|
||||
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', self._mock_os_close_oserror)
|
||||
self.assertRaises(OSError, soc.daemonize, keepfd=None, chdir='./')
|
||||
self.stubs.Set(os, 'close', raise_oserror)
|
||||
self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./')
|
||||
|
||||
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)
|
||||
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(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='./')
|
||||
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_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)
|
||||
def test_handshake_fails_on_not_ready(self):
|
||||
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1)
|
||||
|
||||
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 fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([], [], [])
|
||||
|
||||
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')
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
self.assertRaises(
|
||||
websocket.WebSocketServer.EClose, server.do_handshake,
|
||||
FakeSocket(), '127.0.0.1')
|
||||
|
||||
def test_do_handshake_ssl_error(self):
|
||||
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
|
||||
def test_empty_handshake_fails(self):
|
||||
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1)
|
||||
|
||||
def _mock_wrap_socket(*args, **kwargs):
|
||||
from ssl import SSLError
|
||||
raise SSLError('unit test exception')
|
||||
sock = FakeSocket('')
|
||||
|
||||
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 fake_select(rlist, wlist, xlist, timeout=None):
|
||||
return ([sock], [], [])
|
||||
|
||||
def test_fallback_SIGCHILD(self):
|
||||
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
|
||||
soc.fallback_SIGCHLD(None, None)
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
self.assertRaises(
|
||||
websocket.WebSocketServer.EClose, server.do_handshake,
|
||||
sock, '127.0.0.1')
|
||||
|
||||
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)
|
||||
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(
|
||||
websocket.WebSocketServer.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(
|
||||
websocket.WebSocketServer.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(
|
||||
websocket.WebSocketServer.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(websocket.WebSocketServer, 'socket',
|
||||
lambda *args, **kwargs: sock)
|
||||
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())
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
server.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)
|
||||
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(websocket.WebSocketServer, 'socket',
|
||||
lambda *args, **kwargs: sock)
|
||||
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())
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
server.start_server()
|
||||
|
||||
def test_start_server_systemexit(self):
|
||||
websocket.ssl = None
|
||||
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
|
||||
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(websocket.WebSocketServer, 'socket',
|
||||
lambda *args, **kwargs: sock)
|
||||
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())
|
||||
self.stubs.Set(select, 'select', fake_select)
|
||||
server.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):
|
||||
def test_socket_set_keepalive_options(self):
|
||||
keepcnt = 12
|
||||
keepidle = 34
|
||||
keepintvl = 56
|
||||
|
||||
sock = self.server.socket('localhost',
|
||||
tcp_keepcnt=keepcnt,
|
||||
tcp_keepidle=keepidle,
|
||||
tcp_keepintvl=keepintvl)
|
||||
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)
|
||||
|
||||
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPCNT), keepcnt)
|
||||
@@ -229,11 +331,11 @@ class WebSocketTestCase(unittest.TestCase):
|
||||
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)
|
||||
sock = 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)
|
||||
@@ -241,3 +343,88 @@ class WebSocketTestCase(unittest.TestCase):
|
||||
socket.TCP_KEEPIDLE), keepidle)
|
||||
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
||||
socket.TCP_KEEPINTVL), keepintvl)
|
||||
|
||||
|
||||
class HyBiEncodeDecodeTestCase(unittest.TestCase):
|
||||
def test_decode_hybi_text(self):
|
||||
buf = b'\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58'
|
||||
res = websocket.WebSocketRequestHandler.decode_hybi(buf)
|
||||
|
||||
self.assertEqual(res['fin'], 1)
|
||||
self.assertEqual(res['opcode'], 0x1)
|
||||
self.assertEqual(res['masked'], True)
|
||||
self.assertEqual(res['length'], 5)
|
||||
self.assertEqual(res['payload'], b'Hello')
|
||||
self.assertEqual(res['left'], 0)
|
||||
|
||||
def test_decode_hybi_binary(self):
|
||||
buf = b'\x82\x04\x01\x02\x03\x04'
|
||||
res = websocket.WebSocketRequestHandler.decode_hybi(buf, strict=False)
|
||||
|
||||
self.assertEqual(res['fin'], 1)
|
||||
self.assertEqual(res['opcode'], 0x2)
|
||||
self.assertEqual(res['length'], 4)
|
||||
self.assertEqual(res['payload'], b'\x01\x02\x03\x04')
|
||||
self.assertEqual(res['left'], 0)
|
||||
|
||||
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
|
||||
res = websocket.WebSocketRequestHandler.decode_hybi(buf, strict=False)
|
||||
|
||||
self.assertEqual(res['fin'], 1)
|
||||
self.assertEqual(res['opcode'], 0x2)
|
||||
self.assertEqual(res['length'], 260)
|
||||
self.assertEqual(res['payload'], data)
|
||||
self.assertEqual(res['left'], 0)
|
||||
|
||||
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
|
||||
res = websocket.WebSocketRequestHandler.decode_hybi(buf, strict=False)
|
||||
|
||||
self.assertEqual(res['fin'], 1)
|
||||
self.assertEqual(res['opcode'], 0x2)
|
||||
self.assertEqual(res['length'], 260)
|
||||
self.assertEqual(res['payload'], data)
|
||||
self.assertEqual(res['left'], 0)
|
||||
|
||||
def test_decode_hybi_multi(self):
|
||||
buf1 = b'\x01\x03\x48\x65\x6c'
|
||||
buf2 = b'\x80\x02\x6c\x6f'
|
||||
|
||||
res1 = websocket.WebSocketRequestHandler.decode_hybi(buf1, strict=False)
|
||||
self.assertEqual(res1['fin'], 0)
|
||||
self.assertEqual(res1['opcode'], 0x1)
|
||||
self.assertEqual(res1['length'], 3)
|
||||
self.assertEqual(res1['payload'], b'Hel')
|
||||
self.assertEqual(res1['left'], 0)
|
||||
|
||||
res2 = websocket.WebSocketRequestHandler.decode_hybi(buf2, strict=False)
|
||||
self.assertEqual(res2['fin'], 1)
|
||||
self.assertEqual(res2['opcode'], 0x0)
|
||||
self.assertEqual(res2['length'], 2)
|
||||
self.assertEqual(res2['payload'], b'lo')
|
||||
self.assertEqual(res2['left'], 0)
|
||||
|
||||
def test_encode_hybi_basic(self):
|
||||
res = websocket.WebSocketRequestHandler.encode_hybi(b'Hello', 0x1)
|
||||
expected = (b'\x81\x05\x48\x65\x6c\x6c\x6f', 2, 0)
|
||||
|
||||
self.assertEqual(res, expected)
|
||||
|
||||
def test_strict_mode_refuses_unmasked_client_frames(self):
|
||||
buf = b'\x81\x05\x48\x65\x6c\x6c\x6f'
|
||||
self.assertRaises(websocket.WebSocketRequestHandler.CClose,
|
||||
websocket.WebSocketRequestHandler.decode_hybi,
|
||||
buf)
|
||||
|
||||
def test_no_strict_mode_accepts_unmasked_client_frames(self):
|
||||
buf = b'\x81\x05\x48\x65\x6c\x6c\x6f'
|
||||
res = websocket.WebSocketRequestHandler.decode_hybi(buf, strict=False)
|
||||
|
||||
self.assertEqual(res['fin'], 1)
|
||||
self.assertEqual(res['opcode'], 0x1)
|
||||
self.assertEqual(res['masked'], False)
|
||||
self.assertEqual(res['length'], 5)
|
||||
self.assertEqual(res['payload'], b'Hello')
|
||||
|
||||
@@ -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,122 @@
|
||||
# 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
|
||||
|
||||
import stubout
|
||||
|
||||
from websockify import websocket
|
||||
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(websocket.WebSocketServer, '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_raises_error_on_unknown_token(self):
|
||||
class TestPlugin(token_plugins.BasePlugin):
|
||||
def lookup(self, token):
|
||||
return None
|
||||
|
||||
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"
|
||||
self.assertRaises(FakeServer.EClose, self.handler.get_target,
|
||||
TestPlugin(None), "https://localhost:6080/websockify?token=blah")
|
||||
|
||||
def mock_run_wrap_cmd(*args, **kwargs):
|
||||
web_socket_proxy.__dict__["spawn_message"] = True
|
||||
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, 'run_wrap_cmd', mock_run_wrap_cmd)
|
||||
web_socket_proxy.started()
|
||||
self.assertEquals(web_socket_proxy.__dict__["spawn_message"], True)
|
||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'do_proxy',
|
||||
lambda *args, **kwargs: 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.handler.server.token_plugin = TestPlugin("somehost,")
|
||||
self.handler.new_websocket_client()
|
||||
|
||||
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"
|
||||
self.assertEqual(self.handler.server.target_host, "somehost")
|
||||
self.assertEqual(self.handler.server.target_port, "blah")
|
||||
|
||||
self.stubs.Set(web_socket_proxy, 'socket', MockSocket)
|
||||
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("some error")
|
||||
|
||||
def mock_select(*args, **kwargs):
|
||||
ins = None
|
||||
outs = None
|
||||
excepts = "excepts"
|
||||
return ins, outs, excepts
|
||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'do_proxy',
|
||||
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.new_websocket_client)
|
||||
|
||||
self.handler.server.target_host = "someotherhost"
|
||||
self.handler.new_websocket_client()
|
||||
|
||||
self.stubs.Set(select, 'select', mock_select)
|
||||
self.assertRaises(Exception, web_socket_proxy.new_websocket_client)
|
||||
|
||||
@@ -4,16 +4,13 @@
|
||||
# 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
|
||||
# At some point we should enable this since tox expects it to exist but
|
||||
# the code will need pep8ising first.
|
||||
#[testenv:pep8]
|
||||
#commands = flake8
|
||||
@@ -1,2 +1,2 @@
|
||||
from websocket import *
|
||||
from websocketproxy import *
|
||||
from websockify.websocket import *
|
||||
from websockify.websocketproxy import *
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
class BasePlugin(object):
|
||||
def __init__(self, src=None):
|
||||
self.source = src
|
||||
|
||||
def authenticate(self, headers, target_host, target_port):
|
||||
pass
|
||||
|
||||
|
||||
class AuthenticationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidOriginError(AuthenticationError):
|
||||
def __init__(self, expected, actual):
|
||||
self.expected_origin = expected
|
||||
self.actual_origin = actual
|
||||
|
||||
super(InvalidOriginError, self).__init__(
|
||||
"Invalid Origin Header: Expected one of "
|
||||
"%s, got '%s'" % (expected, actual))
|
||||
|
||||
|
||||
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.getheader('Origin', None)
|
||||
if origin is None or origin not in self.source:
|
||||
raise InvalidOriginError(expected=self.source, actual=origin)
|
||||
@@ -0,0 +1,82 @@
|
||||
import os
|
||||
|
||||
class BasePlugin(object):
|
||||
def __init__(self, src):
|
||||
self.source = src
|
||||
|
||||
def lookup(self, token):
|
||||
return None
|
||||
|
||||
|
||||
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
|
||||
f in os.listdir(self.source)]
|
||||
else:
|
||||
cfg_files = [self.source]
|
||||
|
||||
self._targets = {}
|
||||
for f in cfg_files:
|
||||
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(':')
|
||||
|
||||
def lookup(self, token):
|
||||
if self._targets is None:
|
||||
self._load_targets()
|
||||
|
||||
if token in self._targets:
|
||||
return self._targets[token]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
# the above one is probably more efficient, but this one is
|
||||
# more backwards compatible (although in most cases
|
||||
# ReadOnlyTokenFile should suffice)
|
||||
class TokenFile(ReadOnlyTokenFile):
|
||||
# source is a token file with lines like
|
||||
# token: host:port
|
||||
# or a directory of such files
|
||||
def lookup(self, token):
|
||||
self._load_targets()
|
||||
|
||||
return super(TokenFile, self).lookup(token)
|
||||
|
||||
|
||||
class BaseTokenAPI(BasePlugin):
|
||||
# source is a url with a '%s' in it where the token
|
||||
# should go
|
||||
|
||||
# we import things on demand so that other plugins
|
||||
# in this file can be used w/o unecessary dependencies
|
||||
|
||||
def process_result(self, resp):
|
||||
return resp.text.split(':')
|
||||
|
||||
def lookup(self, token):
|
||||
import requests
|
||||
|
||||
resp = requests.get(self.source % token)
|
||||
|
||||
if resp.ok:
|
||||
return self.process_result(resp)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class JSONTokenApi(BaseTokenAPI):
|
||||
# source is a url with a '%s' in it where the token
|
||||
# should go
|
||||
|
||||
def process_result(self, resp):
|
||||
return (resp.json['host'], resp.json['port'])
|
||||
+52
-20
@@ -104,6 +104,8 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
||||
self.handler_id = getattr(server, "handler_id", False)
|
||||
self.file_only = getattr(server, "file_only", False)
|
||||
self.traffic = getattr(server, "traffic", False)
|
||||
self.auto_pong = getattr(server, "auto_pong", False)
|
||||
self.strict_mode = getattr(server, "strict_mode", True)
|
||||
|
||||
self.logger = getattr(server, "logger", None)
|
||||
if self.logger is None:
|
||||
@@ -118,20 +120,24 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
||||
if numpy:
|
||||
b = c = s2b('')
|
||||
if plen >= 4:
|
||||
mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
|
||||
offset=hlen, count=1)
|
||||
data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
|
||||
offset=pstart, count=int(plen / 4))
|
||||
dtype=numpy.dtype('<u4')
|
||||
if sys.byteorder == 'big':
|
||||
dtype = dtype.newbyteorder('>')
|
||||
mask = numpy.frombuffer(buf, dtype, offset=hlen, count=1)
|
||||
data = numpy.frombuffer(buf, dtype, offset=pstart,
|
||||
count=int(plen / 4))
|
||||
#b = numpy.bitwise_xor(data, mask).data
|
||||
b = numpy.bitwise_xor(data, mask).tostring()
|
||||
|
||||
if plen % 4:
|
||||
#self.msg("Partial unmask")
|
||||
mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
|
||||
offset=hlen, count=(plen % 4))
|
||||
data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
|
||||
offset=pend - (plen % 4),
|
||||
dtype=numpy.dtype('B')
|
||||
if sys.byteorder == 'big':
|
||||
dtype = dtype.newbyteorder('>')
|
||||
mask = numpy.frombuffer(buf, dtype, offset=hlen,
|
||||
count=(plen % 4))
|
||||
data = numpy.frombuffer(buf, dtype,
|
||||
offset=pend - (plen % 4), count=(plen % 4))
|
||||
c = numpy.bitwise_xor(data, mask).tostring()
|
||||
return b + c
|
||||
else:
|
||||
@@ -172,7 +178,7 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
||||
return header + buf, len(header), 0
|
||||
|
||||
@staticmethod
|
||||
def decode_hybi(buf, base64=False, logger=None):
|
||||
def decode_hybi(buf, base64=False, logger=None, strict=True):
|
||||
""" Decode HyBi style WebSocket packets.
|
||||
Returns:
|
||||
{'fin' : 0_or_1,
|
||||
@@ -238,6 +244,10 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
||||
f['length'])
|
||||
else:
|
||||
logger.debug("Unmasked frame: %s" % repr(buf))
|
||||
|
||||
if strict:
|
||||
raise WebSocketRequestHandler.CClose(1002, "The client sent an unmasked frame.")
|
||||
|
||||
f['payload'] = buf[(f['hlen'] + f['masked'] * 4):full_len]
|
||||
|
||||
if base64 and f['opcode'] in [1, 2]:
|
||||
@@ -346,7 +356,8 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
||||
|
||||
while buf:
|
||||
frame = self.decode_hybi(buf, base64=self.base64,
|
||||
logger=self.logger)
|
||||
logger=self.logger,
|
||||
strict=self.strict_mode)
|
||||
#self.msg("Received buf: %s, frame: %s", repr(buf), frame)
|
||||
|
||||
if frame['payload'] == None:
|
||||
@@ -360,6 +371,15 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
||||
closed = {'code': frame['close_code'],
|
||||
'reason': frame['close_reason']}
|
||||
break
|
||||
elif self.auto_pong and frame['opcode'] == 0x9: # ping
|
||||
self.print_traffic("} ping %s\n" %
|
||||
repr(frame['payload']))
|
||||
self.send_pong(frame['payload'])
|
||||
return [], False
|
||||
elif frame['opcode'] == 0xA: # pong
|
||||
self.print_traffic("} pong %s\n" %
|
||||
repr(frame['payload']))
|
||||
return [], False
|
||||
|
||||
self.print_traffic("}")
|
||||
|
||||
@@ -388,10 +408,20 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
||||
def send_close(self, code=1000, reason=''):
|
||||
""" Send a WebSocket orderly close frame. """
|
||||
|
||||
msg = pack(">H%ds" % len(reason), code, reason)
|
||||
msg = pack(">H%ds" % len(reason), code, s2b(reason))
|
||||
buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
|
||||
self.request.send(buf)
|
||||
|
||||
def send_pong(self, data=''):
|
||||
""" Send a WebSocket pong frame. """
|
||||
buf, h, t = self.encode_hybi(s2b(data), opcode=0x0A, base64=False)
|
||||
self.request.send(buf)
|
||||
|
||||
def send_ping(self, data=''):
|
||||
""" Send a WebSocket ping frame. """
|
||||
buf, h, t = self.encode_hybi(s2b(data), opcode=0x09, base64=False)
|
||||
self.request.send(buf)
|
||||
|
||||
def do_websocket_handshake(self):
|
||||
h = self.headers
|
||||
|
||||
@@ -471,18 +501,18 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
||||
else:
|
||||
self.stype = "Plain non-SSL (ws://)"
|
||||
|
||||
self.log_message("%s: %s WebSocket connection" % (client_addr,
|
||||
self.stype))
|
||||
self.log_message("%s: Version %s, base64: '%s'" % (client_addr,
|
||||
self.version, self.base64))
|
||||
self.log_message("%s: %s WebSocket connection", client_addr,
|
||||
self.stype)
|
||||
self.log_message("%s: Version %s, base64: '%s'", client_addr,
|
||||
self.version, self.base64)
|
||||
if self.path != '/':
|
||||
self.log_message("%s: Path: '%s'" % (client_addr, 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.log_message("opening record file: %s", fname)
|
||||
self.rec = open(fname, 'w+')
|
||||
encoding = "binary"
|
||||
if self.base64: encoding = "base64"
|
||||
@@ -567,7 +597,7 @@ class WebSocketServer(object):
|
||||
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):
|
||||
tcp_keepintvl=None, auto_pong=False, strict_mode=True):
|
||||
|
||||
# settings
|
||||
self.RequestHandlerClass = RequestHandlerClass
|
||||
@@ -582,6 +612,7 @@ class WebSocketServer(object):
|
||||
self.idle_timeout = idle_timeout
|
||||
self.traffic = traffic
|
||||
self.file_only = file_only
|
||||
self.strict_mode = strict_mode
|
||||
|
||||
self.launch_time = time.time()
|
||||
self.ws_connection = False
|
||||
@@ -593,6 +624,7 @@ class WebSocketServer(object):
|
||||
self.tcp_keepidle = tcp_keepidle
|
||||
self.tcp_keepintvl = tcp_keepintvl
|
||||
|
||||
self.auto_pong = auto_pong
|
||||
# Make paths settings absolute
|
||||
self.cert = os.path.abspath(cert)
|
||||
self.key = self.web = self.record = ''
|
||||
@@ -765,7 +797,7 @@ class WebSocketServer(object):
|
||||
handshake = sock.recv(1024, socket.MSG_PEEK)
|
||||
#self.msg("Handshake [%s]" % handshake)
|
||||
|
||||
if handshake == "":
|
||||
if not handshake:
|
||||
raise self.EClose("ignoring empty handshake")
|
||||
|
||||
elif handshake.startswith(s2b("<policy-file-request/>")):
|
||||
@@ -848,7 +880,7 @@ class WebSocketServer(object):
|
||||
raise self.Terminate()
|
||||
|
||||
def multiprocessing_SIGCHLD(self, sig, stack):
|
||||
self.vmsg('Reaing zombies, active child count is %s', len(multiprocessing.active_children()))
|
||||
self.vmsg('Reaping zombies, active child count is %s', len(multiprocessing.active_children()))
|
||||
|
||||
def fallback_SIGCHLD(self, sig, stack):
|
||||
# Reap zombies when using os.fork() (python 2.4)
|
||||
|
||||
+133
-51
@@ -11,13 +11,13 @@ 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
|
||||
import websocket
|
||||
import select
|
||||
from websockify import websocket
|
||||
try:
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
except:
|
||||
@@ -44,8 +44,13 @@ Traffic Legend:
|
||||
"""
|
||||
# Checks if we receive a token, and look
|
||||
# for a valid target for it then
|
||||
if self.server.target_cfg:
|
||||
(self.server.target_host, self.server.target_port) = self.get_target(self.server.target_cfg, self.path)
|
||||
if self.server.token_plugin:
|
||||
(self.server.target_host, self.server.target_port) = self.get_target(self.server.token_plugin, self.path)
|
||||
|
||||
if self.server.auth_plugin:
|
||||
self.server.auth_plugin.authenticate(
|
||||
headers=self.headers, target_host=self.server.target_host,
|
||||
target_port=self.server.target_port)
|
||||
|
||||
# Connect to the target
|
||||
if self.server.wrap_cmd:
|
||||
@@ -74,14 +79,14 @@ Traffic Legend:
|
||||
tsock.shutdown(socket.SHUT_RDWR)
|
||||
tsock.close()
|
||||
if self.verbose:
|
||||
self.log_message("%s:%s: Closed target" %(
|
||||
self.server.target_host, self.server.target_port))
|
||||
self.log_message("%s:%s: Closed target",
|
||||
self.server.target_host, self.server.target_port)
|
||||
raise
|
||||
|
||||
def get_target(self, target_cfg, path):
|
||||
def get_target(self, target_plugin, path):
|
||||
"""
|
||||
Parses the path, extracts a token, and looks for a valid
|
||||
target for that token in the configuration file(s). Sets
|
||||
Parses the path, extracts a token, and looks up a target
|
||||
for that token using the token plugin. Sets
|
||||
target_host and target_port if successful
|
||||
"""
|
||||
# The files in targets contain the lines
|
||||
@@ -90,32 +95,17 @@ Traffic Legend:
|
||||
# Extract the token parameter from url
|
||||
args = parse_qs(urlparse(path)[4]) # 4 is the query from url
|
||||
|
||||
if not args.has_key('token') or not len(args['token']):
|
||||
raise self.EClose("Token not present")
|
||||
if not 'token' in args or not len(args['token']):
|
||||
raise self.server.EClose("Token not present")
|
||||
|
||||
token = args['token'][0].rstrip('\n')
|
||||
|
||||
# target_cfg can be a single config file or directory of
|
||||
# config files
|
||||
if os.path.isdir(target_cfg):
|
||||
cfg_files = [os.path.join(target_cfg, f)
|
||||
for f in os.listdir(target_cfg)]
|
||||
result_pair = target_plugin.lookup(token)
|
||||
|
||||
if result_pair is not None:
|
||||
return result_pair
|
||||
else:
|
||||
cfg_files = [target_cfg]
|
||||
|
||||
targets = {}
|
||||
for f in cfg_files:
|
||||
for line in [l.strip() for l in file(f).readlines()]:
|
||||
if line and not line.startswith('#'):
|
||||
ttoken, target = line.split(': ')
|
||||
targets[ttoken] = target.strip()
|
||||
|
||||
self.vmsg("Target config: %s" % repr(targets))
|
||||
|
||||
if targets.has_key(token):
|
||||
return targets[token].split(':')
|
||||
else:
|
||||
raise self.EClose("Token '%s' not found" % token)
|
||||
raise self.server.EClose("Token '%s' not found" % token)
|
||||
|
||||
def do_proxy(self, target):
|
||||
"""
|
||||
@@ -126,12 +116,37 @@ Traffic Legend:
|
||||
tqueue = []
|
||||
rlist = [self.request, target]
|
||||
|
||||
if self.server.heartbeat:
|
||||
now = time.time()
|
||||
self.heartbeat = now + self.server.heartbeat
|
||||
else:
|
||||
self.heartbeat = None
|
||||
|
||||
while True:
|
||||
wlist = []
|
||||
|
||||
if self.heartbeat is not None:
|
||||
now = time.time()
|
||||
if now > self.heartbeat:
|
||||
self.heartbeat = now + self.server.heartbeat
|
||||
self.send_ping()
|
||||
|
||||
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:
|
||||
@@ -148,8 +163,8 @@ Traffic Legend:
|
||||
if closed:
|
||||
# TODO: What about blocking on client socket?
|
||||
if self.verbose:
|
||||
self.log_message("%s:%s: Client closed connection" %(
|
||||
self.server.target_host, self.server.target_port))
|
||||
self.log_message("%s:%s: Client closed connection",
|
||||
self.server.target_host, self.server.target_port)
|
||||
raise self.CClose(closed['code'], closed['reason'])
|
||||
|
||||
|
||||
@@ -170,8 +185,8 @@ Traffic Legend:
|
||||
buf = target.recv(self.buffer_size)
|
||||
if len(buf) == 0:
|
||||
if self.verbose:
|
||||
self.log_message("%s:%s: Target closed connection" %(
|
||||
self.server.target_host, self.server.target_port))
|
||||
self.log_message("%s:%s: Target closed connection",
|
||||
self.server.target_host, self.server.target_port)
|
||||
raise self.CClose(1000, "Target closed")
|
||||
|
||||
cqueue.append(buf)
|
||||
@@ -195,7 +210,11 @@ class WebSocketProxy(websocket.WebSocketServer):
|
||||
self.wrap_mode = kwargs.pop('wrap_mode', None)
|
||||
self.unix_target = kwargs.pop('unix_target', None)
|
||||
self.ssl_target = kwargs.pop('ssl_target', None)
|
||||
self.target_cfg = kwargs.pop('target_cfg', None)
|
||||
self.heartbeat = kwargs.pop('heartbeat', 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]
|
||||
|
||||
@@ -251,9 +270,9 @@ class WebSocketProxy(websocket.WebSocketServer):
|
||||
else:
|
||||
dst_string = "%s:%s" % (self.target_host, self.target_port)
|
||||
|
||||
if self.target_cfg:
|
||||
msg = " - proxying from %s:%s to targets in %s" % (
|
||||
self.listen_host, self.listen_port, self.target_cfg)
|
||||
if self.token_plugin:
|
||||
msg = " - proxying from %s:%s to targets generated by %s" % (
|
||||
self.listen_host, self.listen_port, type(self.token_plugin).__name__)
|
||||
else:
|
||||
msg = " - proxying from %s:%s to %s" % (
|
||||
self.listen_host, self.listen_port, dst_string)
|
||||
@@ -352,20 +371,56 @@ def websockify_init():
|
||||
parser.add_option("--prefer-ipv6", "-6",
|
||||
action="store_true", dest="source_is_ipv6",
|
||||
help="prefer IPv6 when resolving source_addr")
|
||||
parser.add_option("--libserver", action="store_true",
|
||||
help="use Python library SocketServer engine")
|
||||
parser.add_option("--target-config", metavar="FILE",
|
||||
dest="target_cfg",
|
||||
help="Configuration file containing valid targets "
|
||||
"in the form 'token: host:port' or, alternatively, a "
|
||||
"directory containing configuration files of this form")
|
||||
parser.add_option("--libserver", action="store_true",
|
||||
help="use Python library SocketServer engine")
|
||||
"directory containing configuration files of this form "
|
||||
"(DEPRECATED: use `--token-plugin TokenFile --token-source "
|
||||
" path/to/token/file` instead)")
|
||||
parser.add_option("--token-plugin", default=None, metavar="PLUGIN",
|
||||
help="use the given Python class to process tokens "
|
||||
"into host:port pairs")
|
||||
parser.add_option("--token-source", default=None, metavar="ARG",
|
||||
help="an argument to be passed to the token plugin"
|
||||
"on instantiation")
|
||||
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("--auto-pong", action="store_true",
|
||||
help="Automatically respond to ping frames with a pong")
|
||||
parser.add_option("--heartbeat", type=int, default=0,
|
||||
help="send a ping to the client every HEARTBEAT seconds")
|
||||
|
||||
(opts, args) = parser.parse_args()
|
||||
|
||||
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)
|
||||
|
||||
if opts.target_cfg:
|
||||
opts.token_plugin = 'TokenFile'
|
||||
opts.token_source = opts.target_cfg
|
||||
|
||||
del opts.target_cfg
|
||||
|
||||
# Sanity checks
|
||||
if len(args) < 2 and not (opts.target_cfg or opts.unix_target):
|
||||
if len(args) < 2 and not (opts.token_plugin or opts.unix_target):
|
||||
parser.error("Too few arguments")
|
||||
if sys.argv.count('--'):
|
||||
opts.wrap_cmd = args[1:]
|
||||
@@ -390,7 +445,7 @@ def websockify_init():
|
||||
try: opts.listen_port = int(opts.listen_port)
|
||||
except: parser.error("Error parsing listen port")
|
||||
|
||||
if opts.wrap_cmd or opts.unix_target or opts.target_cfg:
|
||||
if opts.wrap_cmd or opts.unix_target or opts.token_plugin:
|
||||
opts.target_host = None
|
||||
opts.target_port = None
|
||||
else:
|
||||
@@ -402,9 +457,32 @@ def websockify_init():
|
||||
try: opts.target_port = int(opts.target_port)
|
||||
except: parser.error("Error parsing target port")
|
||||
|
||||
# Transform to absolute path as daemon may chdir
|
||||
if opts.target_cfg:
|
||||
opts.target_cfg = os.path.abspath(opts.target_cfg)
|
||||
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
|
||||
@@ -433,9 +511,13 @@ class LibProxyServer(ForkingMixIn, HTTPServer):
|
||||
self.wrap_mode = kwargs.pop('wrap_mode', None)
|
||||
self.unix_target = kwargs.pop('unix_target', None)
|
||||
self.ssl_target = kwargs.pop('ssl_target', None)
|
||||
self.target_cfg = kwargs.pop('target_cfg', None)
|
||||
self.token_plugin = kwargs.pop('token_plugin', 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
|
||||
self.target_cfg = None
|
||||
|
||||
# Server configuration
|
||||
listen_host = kwargs.pop('listen_host', '')
|
||||
|
||||
Reference in New Issue
Block a user