Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd4bc7590e | |||
| d626fed76a |
@@ -9,5 +9,3 @@ other/node_modules
|
|||||||
.pydevproject
|
.pydevproject
|
||||||
target.cfg
|
target.cfg
|
||||||
target.cfg.d
|
target.cfg.d
|
||||||
.tox
|
|
||||||
*.egg-info
|
|
||||||
|
|||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
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,18 +1,6 @@
|
|||||||
Changes
|
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
|
0.6.1 - May 11, 2015
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|||||||
+7
-9
@@ -262,7 +262,7 @@ function on(evt, handler) {
|
|||||||
eventHandlers[evt] = handler;
|
eventHandlers[evt] = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(protocols, ws_schema) {
|
function init(protocols) {
|
||||||
rQ = [];
|
rQ = [];
|
||||||
rQi = 0;
|
rQi = 0;
|
||||||
sQ = [];
|
sQ = [];
|
||||||
@@ -277,13 +277,12 @@ function init(protocols, ws_schema) {
|
|||||||
('set' in Uint8Array.prototype)) {
|
('set' in Uint8Array.prototype)) {
|
||||||
bt = true;
|
bt = true;
|
||||||
}
|
}
|
||||||
// Check for full binary type support in WebSocket
|
|
||||||
// Inspired by:
|
// Check for full binary type support in WebSockets
|
||||||
// https://github.com/Modernizr/Modernizr/issues/370
|
// TODO: this sucks, the property should exist on the prototype
|
||||||
// https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets/binary.js
|
// but it does not.
|
||||||
try {
|
try {
|
||||||
if (bt && ('binaryType' in WebSocket.prototype ||
|
if (bt && ('binaryType' in (new WebSocket("ws://localhost:17523")))) {
|
||||||
!!(new WebSocket(ws_schema + '://.').binaryType))) {
|
|
||||||
Util.Info("Detected binaryType support in WebSockets");
|
Util.Info("Detected binaryType support in WebSockets");
|
||||||
wsbt = true;
|
wsbt = true;
|
||||||
}
|
}
|
||||||
@@ -326,8 +325,7 @@ function init(protocols, ws_schema) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function open(uri, protocols) {
|
function open(uri, protocols) {
|
||||||
var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
|
protocols = init(protocols);
|
||||||
protocols = init(protocols, ws_schema);
|
|
||||||
|
|
||||||
if (test_mode) {
|
if (test_mode) {
|
||||||
websocket = {};
|
websocket = {};
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
"name": "websockify",
|
"name": "websockify",
|
||||||
"description": "websockify is a WebSocket-to-TCP proxy/bridge",
|
"description": "websockify is a WebSocket-to-TCP proxy/bridge",
|
||||||
"license": "LGPL-3",
|
"license": "LGPL-3",
|
||||||
"version": "0.7.0",
|
"version": "0.6.1",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/kanaka/websockify.git"
|
"url": "git://github.com/kanaka/websockify.git"
|
||||||
},
|
},
|
||||||
"files": ["../../docs/LICENSE.LGPL-3","websockify.js"],
|
"files": ["../../docs/LICENSE.LGPL-3"],
|
||||||
"bin": {
|
"bin": {
|
||||||
"websockify": "./websockify.js"
|
"websockify": "./websockify.js"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/python
|
||||||
|
|
||||||
import websockify
|
import websockify
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
version = '0.7.0'
|
version = '0.6.1'
|
||||||
name = 'websockify'
|
name = 'websockify'
|
||||||
long_description = open("README.md").read() + "\n" + \
|
long_description = open("README.md").read() + "\n" + \
|
||||||
open("CHANGES.txt").read() + "\n"
|
open("CHANGES.txt").read() + "\n"
|
||||||
@@ -11,12 +11,6 @@ setup(name=name,
|
|||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Programming Language :: Python",
|
"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',
|
data_files=[('share/websockify/include',
|
||||||
['include/util.js',
|
['include/util.js',
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
mox
|
|
||||||
nose
|
|
||||||
+151
-338
@@ -26,303 +26,201 @@ import stubout
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
import socket
|
from ssl import SSLError
|
||||||
import signal
|
from websockify import websocket as websocket
|
||||||
from websockify import websocket
|
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
def raise_oserror(*args, **kwargs):
|
class WebSocketTestCase(unittest.TestCase):
|
||||||
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):
|
def setUp(self):
|
||||||
super(WebSocketRequestHandlerTestCase, self).setUp()
|
"""Called automatically before each test."""
|
||||||
|
super(WebSocketTestCase, self).setUp()
|
||||||
self.stubs = stubout.StubOutForTesting()
|
self.stubs = stubout.StubOutForTesting()
|
||||||
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
|
# Temporary dir for test data
|
||||||
|
self.tmpdir = tempfile.mkdtemp()
|
||||||
|
# Put log somewhere persistent
|
||||||
|
self._init_logger('./')
|
||||||
# Mock this out cause it screws tests up
|
# Mock this out cause it screws tests up
|
||||||
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
|
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
|
||||||
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
|
self.server = self._get_websockserver(daemon=True,
|
||||||
lambda *args, **kwargs: None)
|
ssl_only=False)
|
||||||
|
self.soc = self.server.socket('localhost')
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""Called automatically after each test."""
|
"""Called automatically after each test."""
|
||||||
self.stubs.UnsetAll()
|
self.stubs.UnsetAll()
|
||||||
os.rmdir(self.tmpdir)
|
shutil.rmtree(self.tmpdir)
|
||||||
super(WebSocketRequestHandlerTestCase, self).tearDown()
|
super(WebSocketTestCase, self).tearDown()
|
||||||
|
|
||||||
def _get_server(self, handler_class=websocket.WebSocketRequestHandler,
|
def _get_websockserver(self, **kwargs):
|
||||||
**kwargs):
|
return websocket.WebSocketServer(listen_host='localhost',
|
||||||
web = kwargs.pop('web', self.tmpdir)
|
listen_port=80,
|
||||||
return websocket.WebSocketServer(
|
key=self.tmpdir,
|
||||||
handler_class, listen_host='localhost',
|
web=self.tmpdir,
|
||||||
listen_port=80, key=self.tmpdir, web=web,
|
record=self.tmpdir,
|
||||||
record=self.tmpdir, daemon=False, ssl_only=0, idle_timeout=1,
|
**kwargs)
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
def test_normal_get_with_only_upgrade_returns_error(self):
|
def _mock_os_open_oserror(self, file, flags):
|
||||||
server = self._get_server(web=None)
|
raise OSError('')
|
||||||
handler = websocket.WebSocketRequestHandler(
|
|
||||||
FakeSocket('GET /tmp.txt HTTP/1.1'), '127.0.0.1', server)
|
|
||||||
|
|
||||||
def fake_send_response(self, code, message=None):
|
def _mock_os_close_oserror(self, fd):
|
||||||
self.last_code = code
|
raise OSError('')
|
||||||
|
|
||||||
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
|
def _mock_os_close_oserror_EBADF(self, fd):
|
||||||
fake_send_response)
|
raise OSError(errno.EBADF, '')
|
||||||
|
|
||||||
handler.do_GET()
|
def _mock_socket(self, *args, **kwargs):
|
||||||
self.assertEqual(handler.last_code, 405)
|
return self.soc
|
||||||
|
|
||||||
def test_list_dir_with_file_only_returns_error(self):
|
def _mock_select(self, rlist, wlist, xlist, timeout=None):
|
||||||
server = self._get_server(file_only=True)
|
return '_mock_select'
|
||||||
handler = websocket.WebSocketRequestHandler(
|
|
||||||
FakeSocket('GET / HTTP/1.1'), '127.0.0.1', server)
|
|
||||||
|
|
||||||
def fake_send_response(self, code, message=None):
|
def _mock_select_exception(self, rlist, wlist, xlist, timeout=None):
|
||||||
self.last_code = code
|
raise Exception
|
||||||
|
|
||||||
self.stubs.Set(SimpleHTTPRequestHandler, 'send_response',
|
def _mock_select_keyboardinterrupt(self, rlist, wlist,
|
||||||
fake_send_response)
|
xlist, timeout=None):
|
||||||
|
raise KeyboardInterrupt
|
||||||
|
|
||||||
handler.path = '/'
|
def _mock_select_systemexit(self, rlist, wlist, xlist, timeout=None):
|
||||||
handler.do_GET()
|
sys.exit()
|
||||||
self.assertEqual(handler.last_code, 404)
|
|
||||||
|
|
||||||
|
def test_daemonize_error(self):
|
||||||
class WebSocketServerTestCase(unittest.TestCase):
|
soc = self._get_websockserver(daemon=True, ssl_only=1, idle_timeout=1)
|
||||||
def setUp(self):
|
self.stubs.Set(os, 'fork', lambda *args: None)
|
||||||
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, 'setsid', lambda *args: None)
|
||||||
self.stubs.Set(os, 'close', raise_oserror)
|
self.stubs.Set(os, 'close', self._mock_os_close_oserror)
|
||||||
self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./')
|
self.assertRaises(OSError, soc.daemonize, keepfd=None, chdir='./')
|
||||||
|
|
||||||
def test_daemonize_ignores_ebadf_error_while_closing_fds(self):
|
def test_daemonize_EBADF_error(self):
|
||||||
def raise_oserror_ebadf(fd):
|
soc = self._get_websockserver(daemon=True, ssl_only=1, idle_timeout=1)
|
||||||
raise OSError(errno.EBADF, 'fake error')
|
self.stubs.Set(os, 'fork', lambda *args: None)
|
||||||
|
|
||||||
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, 'setsid', lambda *args: None)
|
||||||
self.stubs.Set(signal, 'signal', lambda *args: None)
|
self.stubs.Set(os, 'close', self._mock_os_close_oserror_EBADF)
|
||||||
self.stubs.Set(os, 'close', raise_oserror_ebadf)
|
self.stubs.Set(os, 'open', self._mock_os_open_oserror)
|
||||||
self.stubs.Set(os, 'open', raise_oserror)
|
self.assertRaises(OSError, soc.daemonize, keepfd=None, chdir='./')
|
||||||
self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./')
|
|
||||||
|
|
||||||
def test_handshake_fails_on_not_ready(self):
|
def test_decode_hybi(self):
|
||||||
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1)
|
soc = self._get_websockserver(daemon=False, ssl_only=1, idle_timeout=1)
|
||||||
|
self.assertRaises(Exception, soc.decode_hybi, 'a' * 128,
|
||||||
|
base64=True)
|
||||||
|
|
||||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
def test_do_websocket_handshake(self):
|
||||||
return ([], [], [])
|
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')
|
||||||
|
|
||||||
self.stubs.Set(select, 'select', fake_select)
|
def test_do_handshake(self):
|
||||||
self.assertRaises(
|
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
|
||||||
websocket.WebSocketServer.EClose, server.do_handshake,
|
self.stubs.Set(select, 'select', self._mock_select)
|
||||||
FakeSocket(), '127.0.0.1')
|
self.stubs.Set(socket._socketobject, 'recv', lambda *args: 'mock_recv')
|
||||||
|
self.assertRaises(Exception, soc.do_handshake, self.soc, '127.0.0.1')
|
||||||
|
|
||||||
def test_empty_handshake_fails(self):
|
def test_do_handshake_ssl_error(self):
|
||||||
server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1)
|
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
|
||||||
|
|
||||||
sock = FakeSocket('')
|
def _mock_wrap_socket(*args, **kwargs):
|
||||||
|
from ssl import SSLError
|
||||||
|
raise SSLError('unit test exception')
|
||||||
|
|
||||||
def fake_select(rlist, wlist, xlist, timeout=None):
|
self.stubs.Set(select, 'select', self._mock_select)
|
||||||
return ([sock], [], [])
|
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')
|
||||||
|
|
||||||
self.stubs.Set(select, 'select', fake_select)
|
def test_fallback_SIGCHILD(self):
|
||||||
self.assertRaises(
|
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1)
|
||||||
websocket.WebSocketServer.EClose, server.do_handshake,
|
soc.fallback_SIGCHLD(None, None)
|
||||||
sock, '127.0.0.1')
|
|
||||||
|
|
||||||
def test_handshake_policy_request(self):
|
def test_start_server_Exception(self):
|
||||||
# TODO(directxman12): implement
|
soc = self._get_websockserver(daemon=False, ssl_only=1, idle_timeout=1)
|
||||||
pass
|
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
|
||||||
|
|
||||||
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',
|
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
|
||||||
lambda *args, **kwargs: None)
|
lambda *args, **kwargs: None)
|
||||||
self.stubs.Set(select, 'select', fake_select)
|
self.stubs.Set(select, 'select', self._mock_select_exception)
|
||||||
server.start_server()
|
self.assertEqual(None, soc.start_server())
|
||||||
|
|
||||||
def test_start_server_keyboardinterrupt(self):
|
def test_start_server_KeyboardInterrupt(self):
|
||||||
server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1)
|
soc = self._get_websockserver(daemon=False, ssl_only=1, idle_timeout=1)
|
||||||
sock = server.socket('localhost')
|
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
|
||||||
|
|
||||||
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',
|
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
|
||||||
lambda *args, **kwargs: None)
|
lambda *args, **kwargs: None)
|
||||||
self.stubs.Set(select, 'select', fake_select)
|
self.stubs.Set(select, 'select', self._mock_select_keyboardinterrupt)
|
||||||
server.start_server()
|
self.assertEqual(None, soc.start_server())
|
||||||
|
|
||||||
def test_start_server_systemexit(self):
|
def test_start_server_systemexit(self):
|
||||||
server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1)
|
websocket.ssl = None
|
||||||
sock = server.socket('localhost')
|
self.stubs.Set(websocket.WebSocketServer, 'socket', self._mock_socket)
|
||||||
|
|
||||||
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',
|
self.stubs.Set(websocket.WebSocketServer, 'daemonize',
|
||||||
lambda *args, **kwargs: None)
|
lambda *args, **kwargs: None)
|
||||||
self.stubs.Set(select, 'select', fake_select)
|
self.stubs.Set(select, 'select', self._mock_select_systemexit)
|
||||||
server.start_server()
|
soc = self._get_websockserver(daemon=True, ssl_only=0, idle_timeout=1,
|
||||||
|
verbose=True)
|
||||||
|
self.assertEqual(None, soc.start_server())
|
||||||
|
|
||||||
def test_socket_set_keepalive_options(self):
|
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
|
keepcnt = 12
|
||||||
keepidle = 34
|
keepidle = 34
|
||||||
keepintvl = 56
|
keepintvl = 56
|
||||||
|
|
||||||
server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1)
|
sock = self.server.socket('localhost',
|
||||||
sock = server.socket('localhost',
|
tcp_keepcnt=keepcnt,
|
||||||
tcp_keepcnt=keepcnt,
|
tcp_keepidle=keepidle,
|
||||||
tcp_keepidle=keepidle,
|
tcp_keepintvl=keepintvl)
|
||||||
tcp_keepintvl=keepintvl)
|
|
||||||
|
|
||||||
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
||||||
socket.TCP_KEEPCNT), keepcnt)
|
socket.TCP_KEEPCNT), keepcnt)
|
||||||
@@ -331,11 +229,11 @@ class WebSocketServerTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
self.assertEqual(sock.getsockopt(socket.SOL_TCP,
|
||||||
socket.TCP_KEEPINTVL), keepintvl)
|
socket.TCP_KEEPINTVL), keepintvl)
|
||||||
|
|
||||||
sock = server.socket('localhost',
|
sock = self.server.socket('localhost',
|
||||||
tcp_keepalive=False,
|
tcp_keepalive=False,
|
||||||
tcp_keepcnt=keepcnt,
|
tcp_keepcnt=keepcnt,
|
||||||
tcp_keepidle=keepidle,
|
tcp_keepidle=keepidle,
|
||||||
tcp_keepintvl=keepintvl)
|
tcp_keepintvl=keepintvl)
|
||||||
|
|
||||||
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
||||||
socket.TCP_KEEPCNT), keepcnt)
|
socket.TCP_KEEPCNT), keepcnt)
|
||||||
@@ -343,88 +241,3 @@ class WebSocketServerTestCase(unittest.TestCase):
|
|||||||
socket.TCP_KEEPIDLE), keepidle)
|
socket.TCP_KEEPIDLE), keepidle)
|
||||||
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
self.assertNotEqual(sock.getsockopt(socket.SOL_TCP,
|
||||||
socket.TCP_KEEPINTVL), keepintvl)
|
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
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
# Copyright(c) 2015 Red Hat, Inc All Rights Reserved.
|
# Copyright(c)2013 NTT corp. All Rights Reserved.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# 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
|
# not use this file except in compliance with the License. You may obtain
|
||||||
@@ -15,122 +15,113 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
""" Unit tests for websocketproxy """
|
""" Unit tests for websocketproxy """
|
||||||
|
import os
|
||||||
import unittest
|
import logging
|
||||||
import unittest
|
import select
|
||||||
import socket
|
import shutil
|
||||||
|
|
||||||
import stubout
|
import stubout
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
|
||||||
from websockify import websocket
|
|
||||||
from websockify import websocketproxy
|
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 FakeSocket(object):
|
class MockSocket(object):
|
||||||
def __init__(self, data=''):
|
def __init__(*args, **kwargs):
|
||||||
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
|
pass
|
||||||
|
|
||||||
def __init__(self):
|
def shutdown(*args):
|
||||||
self.token_plugin = None
|
pass
|
||||||
self.auth_plugin = None
|
|
||||||
self.wrap_cmd = None
|
def close(*args):
|
||||||
self.ssl_target = None
|
pass
|
||||||
self.unix_target = None
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
class ProxyRequestHandlerTestCase(unittest.TestCase):
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ProxyRequestHandlerTestCase, self).setUp()
|
"""Called automatically before each test."""
|
||||||
|
super(WebSocketProxyTest, self).setUp()
|
||||||
|
self.soc = ''
|
||||||
self.stubs = stubout.StubOutForTesting()
|
self.stubs = stubout.StubOutForTesting()
|
||||||
self.handler = websocketproxy.ProxyRequestHandler(
|
# Temporary dir for test data
|
||||||
FakeSocket(''), "127.0.0.1", FakeServer())
|
self.tmpdir = tempfile.mkdtemp()
|
||||||
self.handler.path = "https://localhost:6080/websockify?token=blah"
|
# Put log somewhere persistent
|
||||||
self.handler.headers = None
|
self._init_logger('./')
|
||||||
self.stubs.Set(websocket.WebSocketServer, 'socket',
|
# Mock this out cause it screws tests up
|
||||||
staticmethod(lambda *args, **kwargs: None))
|
self.stubs.Set(os, 'chdir', lambda *args, **kwargs: None)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
"""Called automatically after each test."""
|
||||||
self.stubs.UnsetAll()
|
self.stubs.UnsetAll()
|
||||||
super(ProxyRequestHandlerTestCase, self).tearDown()
|
shutil.rmtree(self.tmpdir)
|
||||||
|
super(WebSocketProxyTest, self).tearDown()
|
||||||
|
|
||||||
def test_get_target(self):
|
def _get_websockproxy(self, **kwargs):
|
||||||
class TestPlugin(token_plugins.BasePlugin):
|
return websocketproxy.WebSocketProxy(key=self.tmpdir,
|
||||||
def lookup(self, token):
|
web=self.tmpdir,
|
||||||
return ("some host", "some port")
|
record=self.tmpdir,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
host, port = self.handler.get_target(
|
def test_run_wrap_cmd(self):
|
||||||
TestPlugin(None), self.handler.path)
|
web_socket_proxy = self._get_websockproxy()
|
||||||
|
web_socket_proxy.__dict__["wrap_cmd"] = "wrap_cmd"
|
||||||
|
|
||||||
self.assertEqual(host, "some host")
|
def mock_Popen(*args, **kwargs):
|
||||||
self.assertEqual(port, "some port")
|
return '_mock_cmd'
|
||||||
|
|
||||||
def test_get_target_raises_error_on_unknown_token(self):
|
self.stubs.Set(subprocess, 'Popen', mock_Popen)
|
||||||
class TestPlugin(token_plugins.BasePlugin):
|
web_socket_proxy.run_wrap_cmd()
|
||||||
def lookup(self, token):
|
self.assertEquals(web_socket_proxy.spawn_message, True)
|
||||||
return None
|
|
||||||
|
|
||||||
self.assertRaises(FakeServer.EClose, self.handler.get_target,
|
def test_started(self):
|
||||||
TestPlugin(None), "https://localhost:6080/websockify?token=blah")
|
web_socket_proxy = self._get_websockproxy()
|
||||||
|
web_socket_proxy.__dict__["spawn_message"] = False
|
||||||
|
web_socket_proxy.__dict__["wrap_cmd"] = "wrap_cmd"
|
||||||
|
|
||||||
def test_token_plugin(self):
|
def mock_run_wrap_cmd(*args, **kwargs):
|
||||||
class TestPlugin(token_plugins.BasePlugin):
|
web_socket_proxy.__dict__["spawn_message"] = True
|
||||||
def lookup(self, token):
|
|
||||||
return (self.source + token).split(',')
|
|
||||||
|
|
||||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'do_proxy',
|
self.stubs.Set(web_socket_proxy, 'run_wrap_cmd', mock_run_wrap_cmd)
|
||||||
lambda *args, **kwargs: None)
|
web_socket_proxy.started()
|
||||||
|
self.assertEquals(web_socket_proxy.__dict__["spawn_message"], True)
|
||||||
|
|
||||||
self.handler.server.token_plugin = TestPlugin("somehost,")
|
def test_poll(self):
|
||||||
self.handler.new_websocket_client()
|
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.assertEqual(self.handler.server.target_host, "somehost")
|
def test_new_client(self):
|
||||||
self.assertEqual(self.handler.server.target_port, "blah")
|
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_auth_plugin(self):
|
self.stubs.Set(web_socket_proxy, 'socket', MockSocket)
|
||||||
class TestPlugin(auth_plugins.BasePlugin):
|
|
||||||
def authenticate(self, headers, target_host, target_port):
|
|
||||||
if target_host == self.source:
|
|
||||||
raise auth_plugins.AuthenticationError("some error")
|
|
||||||
|
|
||||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'do_proxy',
|
def mock_select(*args, **kwargs):
|
||||||
staticmethod(lambda *args, **kwargs: None))
|
ins = None
|
||||||
|
outs = None
|
||||||
self.handler.server.auth_plugin = TestPlugin("somehost")
|
excepts = "excepts"
|
||||||
self.handler.server.target_host = "somehost"
|
return ins, outs, excepts
|
||||||
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,14 +4,17 @@
|
|||||||
# and then run "tox" from this directory.
|
# and then run "tox" from this directory.
|
||||||
|
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py24,py26,py27,py33,py34
|
envlist = py24,py25,py26,py27,py30
|
||||||
|
setupdir = ../
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
commands = nosetests {posargs}
|
commands = nosetests {posargs}
|
||||||
deps = -r{toxinidir}/test-requirements.txt
|
deps =
|
||||||
|
mox
|
||||||
|
nose
|
||||||
|
|
||||||
# At some point we should enable this since tox expects it to exist but
|
# At some point we should enable this since tox epdctes it to exist but
|
||||||
# the code will need pep8ising first.
|
# the code will need pep8ising first.
|
||||||
#[testenv:pep8]
|
#[testenv:pep8]
|
||||||
#commands = flake8
|
#commands = flake8
|
||||||
#dep = flake8
|
#dep = flake8
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
from websockify.websocket import *
|
from websocket import *
|
||||||
from websockify.websocketproxy import *
|
from websocketproxy import *
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
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'])
|
|
||||||
+32
-64
@@ -74,8 +74,8 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
|||||||
attributes on the server object:
|
attributes on the server object:
|
||||||
|
|
||||||
* only_upgrade: If true, SimpleHTTPRequestHandler will not be enabled,
|
* only_upgrade: If true, SimpleHTTPRequestHandler will not be enabled,
|
||||||
only websocket is allowed.
|
only websocket is allowed.
|
||||||
* verbose: If true, verbose logging is activated.
|
* verbose: If true, verbose logging is activated.
|
||||||
* daemon: Running as daemon, do not write to console etc
|
* daemon: Running as daemon, do not write to console etc
|
||||||
* record: Record raw frame data as JavaScript array into specified filename
|
* record: Record raw frame data as JavaScript array into specified filename
|
||||||
* run_once: Handle a single request
|
* run_once: Handle a single request
|
||||||
@@ -104,13 +104,11 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
|||||||
self.handler_id = getattr(server, "handler_id", False)
|
self.handler_id = getattr(server, "handler_id", False)
|
||||||
self.file_only = getattr(server, "file_only", False)
|
self.file_only = getattr(server, "file_only", False)
|
||||||
self.traffic = getattr(server, "traffic", 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)
|
self.logger = getattr(server, "logger", None)
|
||||||
if self.logger is None:
|
if self.logger is None:
|
||||||
self.logger = WebSocketServer.get_logger()
|
self.logger = WebSocketServer.get_logger()
|
||||||
|
|
||||||
SimpleHTTPRequestHandler.__init__(self, req, addr, server)
|
SimpleHTTPRequestHandler.__init__(self, req, addr, server)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -120,24 +118,20 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
|||||||
if numpy:
|
if numpy:
|
||||||
b = c = s2b('')
|
b = c = s2b('')
|
||||||
if plen >= 4:
|
if plen >= 4:
|
||||||
dtype=numpy.dtype('<u4')
|
mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
|
||||||
if sys.byteorder == 'big':
|
offset=hlen, count=1)
|
||||||
dtype = dtype.newbyteorder('>')
|
data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
|
||||||
mask = numpy.frombuffer(buf, dtype, offset=hlen, count=1)
|
offset=pstart, count=int(plen / 4))
|
||||||
data = numpy.frombuffer(buf, dtype, offset=pstart,
|
|
||||||
count=int(plen / 4))
|
|
||||||
#b = numpy.bitwise_xor(data, mask).data
|
#b = numpy.bitwise_xor(data, mask).data
|
||||||
b = numpy.bitwise_xor(data, mask).tostring()
|
b = numpy.bitwise_xor(data, mask).tostring()
|
||||||
|
|
||||||
if plen % 4:
|
if plen % 4:
|
||||||
#self.msg("Partial unmask")
|
#self.msg("Partial unmask")
|
||||||
dtype=numpy.dtype('B')
|
mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
|
||||||
if sys.byteorder == 'big':
|
offset=hlen, count=(plen % 4))
|
||||||
dtype = dtype.newbyteorder('>')
|
data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
|
||||||
mask = numpy.frombuffer(buf, dtype, offset=hlen,
|
offset=pend - (plen % 4),
|
||||||
count=(plen % 4))
|
count=(plen % 4))
|
||||||
data = numpy.frombuffer(buf, dtype,
|
|
||||||
offset=pend - (plen % 4), count=(plen % 4))
|
|
||||||
c = numpy.bitwise_xor(data, mask).tostring()
|
c = numpy.bitwise_xor(data, mask).tostring()
|
||||||
return b + c
|
return b + c
|
||||||
else:
|
else:
|
||||||
@@ -178,7 +172,7 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
|||||||
return header + buf, len(header), 0
|
return header + buf, len(header), 0
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def decode_hybi(buf, base64=False, logger=None, strict=True):
|
def decode_hybi(buf, base64=False, logger=None):
|
||||||
""" Decode HyBi style WebSocket packets.
|
""" Decode HyBi style WebSocket packets.
|
||||||
Returns:
|
Returns:
|
||||||
{'fin' : 0_or_1,
|
{'fin' : 0_or_1,
|
||||||
@@ -244,10 +238,6 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
|||||||
f['length'])
|
f['length'])
|
||||||
else:
|
else:
|
||||||
logger.debug("Unmasked frame: %s" % repr(buf))
|
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]
|
f['payload'] = buf[(f['hlen'] + f['masked'] * 4):full_len]
|
||||||
|
|
||||||
if base64 and f['opcode'] in [1, 2]:
|
if base64 and f['opcode'] in [1, 2]:
|
||||||
@@ -356,8 +346,7 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
|||||||
|
|
||||||
while buf:
|
while buf:
|
||||||
frame = self.decode_hybi(buf, base64=self.base64,
|
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)
|
#self.msg("Received buf: %s, frame: %s", repr(buf), frame)
|
||||||
|
|
||||||
if frame['payload'] == None:
|
if frame['payload'] == None:
|
||||||
@@ -371,15 +360,6 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
|||||||
closed = {'code': frame['close_code'],
|
closed = {'code': frame['close_code'],
|
||||||
'reason': frame['close_reason']}
|
'reason': frame['close_reason']}
|
||||||
break
|
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("}")
|
self.print_traffic("}")
|
||||||
|
|
||||||
@@ -408,20 +388,10 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
|||||||
def send_close(self, code=1000, reason=''):
|
def send_close(self, code=1000, reason=''):
|
||||||
""" Send a WebSocket orderly close frame. """
|
""" Send a WebSocket orderly close frame. """
|
||||||
|
|
||||||
msg = pack(">H%ds" % len(reason), code, s2b(reason))
|
msg = pack(">H%ds" % len(reason), code, reason)
|
||||||
buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
|
buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
|
||||||
self.request.send(buf)
|
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):
|
def do_websocket_handshake(self):
|
||||||
h = self.headers
|
h = self.headers
|
||||||
|
|
||||||
@@ -465,16 +435,16 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
|||||||
self.send_header("Sec-WebSocket-Protocol", "binary")
|
self.send_header("Sec-WebSocket-Protocol", "binary")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.send_error(400, "Missing Sec-WebSocket-Version header. Hixie protocols not supported.")
|
self.send_error(400, "Missing Sec-WebSocket-Version header. Hixie protocols not supported.")
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def handle_websocket(self):
|
def handle_websocket(self):
|
||||||
"""Upgrade a connection to Websocket, if requested. If this succeeds,
|
"""Upgrade a connection to Websocket, if requested. If this succeeds,
|
||||||
new_websocket_client() will be called. Otherwise, False is returned.
|
new_websocket_client() will be called. Otherwise, False is returned.
|
||||||
"""
|
"""
|
||||||
if (self.headers.get('upgrade') and
|
if (self.headers.get('upgrade') and
|
||||||
self.headers.get('upgrade').lower() == 'websocket'):
|
self.headers.get('upgrade').lower() == 'websocket'):
|
||||||
|
|
||||||
if not self.do_websocket_handshake():
|
if not self.do_websocket_handshake():
|
||||||
@@ -498,21 +468,21 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
|||||||
|
|
||||||
if is_ssl:
|
if is_ssl:
|
||||||
self.stype = "SSL/TLS (wss://)"
|
self.stype = "SSL/TLS (wss://)"
|
||||||
else:
|
else:
|
||||||
self.stype = "Plain non-SSL (ws://)"
|
self.stype = "Plain non-SSL (ws://)"
|
||||||
|
|
||||||
self.log_message("%s: %s WebSocket connection", client_addr,
|
self.log_message("%s: %s WebSocket connection" % (client_addr,
|
||||||
self.stype)
|
self.stype))
|
||||||
self.log_message("%s: Version %s, base64: '%s'", client_addr,
|
self.log_message("%s: Version %s, base64: '%s'" % (client_addr,
|
||||||
self.version, self.base64)
|
self.version, self.base64))
|
||||||
if self.path != '/':
|
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:
|
if self.record:
|
||||||
# Record raw frame data as JavaScript array
|
# Record raw frame data as JavaScript array
|
||||||
fname = "%s.%s" % (self.record,
|
fname = "%s.%s" % (self.record,
|
||||||
self.handler_id)
|
self.handler_id)
|
||||||
self.log_message("opening record file: %s", fname)
|
self.log_message("opening record file: %s" % fname)
|
||||||
self.rec = open(fname, 'w+')
|
self.rec = open(fname, 'w+')
|
||||||
encoding = "binary"
|
encoding = "binary"
|
||||||
if self.base64: encoding = "base64"
|
if self.base64: encoding = "base64"
|
||||||
@@ -525,7 +495,7 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
|||||||
except self.CClose:
|
except self.CClose:
|
||||||
# Close the client
|
# Close the client
|
||||||
_, exc, _ = sys.exc_info()
|
_, exc, _ = sys.exc_info()
|
||||||
self.send_close(exc.args[0], exc.args[1])
|
self.send_close(exc.args[0], exc.args[1])
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
@@ -544,7 +514,7 @@ class WebSocketRequestHandler(SimpleHTTPRequestHandler):
|
|||||||
self.send_error(404, "No such file")
|
self.send_error(404, "No such file")
|
||||||
else:
|
else:
|
||||||
return SimpleHTTPRequestHandler.list_directory(self, path)
|
return SimpleHTTPRequestHandler.list_directory(self, path)
|
||||||
|
|
||||||
def new_websocket_client(self):
|
def new_websocket_client(self):
|
||||||
""" Do something with a WebSockets client connection. """
|
""" Do something with a WebSockets client connection. """
|
||||||
raise Exception("WebSocketRequestHandler.new_websocket_client() must be overloaded")
|
raise Exception("WebSocketRequestHandler.new_websocket_client() must be overloaded")
|
||||||
@@ -590,14 +560,14 @@ class WebSocketServer(object):
|
|||||||
class Terminate(Exception):
|
class Terminate(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self, RequestHandlerClass, listen_host='',
|
def __init__(self, RequestHandlerClass, listen_host='',
|
||||||
listen_port=None, source_is_ipv6=False,
|
listen_port=None, source_is_ipv6=False,
|
||||||
verbose=False, cert='', key='', ssl_only=None,
|
verbose=False, cert='', key='', ssl_only=None,
|
||||||
daemon=False, record='', web='',
|
daemon=False, record='', web='',
|
||||||
file_only=False,
|
file_only=False,
|
||||||
run_once=False, timeout=0, idle_timeout=0, traffic=False,
|
run_once=False, timeout=0, idle_timeout=0, traffic=False,
|
||||||
tcp_keepalive=True, tcp_keepcnt=None, tcp_keepidle=None,
|
tcp_keepalive=True, tcp_keepcnt=None, tcp_keepidle=None,
|
||||||
tcp_keepintvl=None, auto_pong=False, strict_mode=True):
|
tcp_keepintvl=None):
|
||||||
|
|
||||||
# settings
|
# settings
|
||||||
self.RequestHandlerClass = RequestHandlerClass
|
self.RequestHandlerClass = RequestHandlerClass
|
||||||
@@ -612,7 +582,6 @@ class WebSocketServer(object):
|
|||||||
self.idle_timeout = idle_timeout
|
self.idle_timeout = idle_timeout
|
||||||
self.traffic = traffic
|
self.traffic = traffic
|
||||||
self.file_only = file_only
|
self.file_only = file_only
|
||||||
self.strict_mode = strict_mode
|
|
||||||
|
|
||||||
self.launch_time = time.time()
|
self.launch_time = time.time()
|
||||||
self.ws_connection = False
|
self.ws_connection = False
|
||||||
@@ -624,7 +593,6 @@ class WebSocketServer(object):
|
|||||||
self.tcp_keepidle = tcp_keepidle
|
self.tcp_keepidle = tcp_keepidle
|
||||||
self.tcp_keepintvl = tcp_keepintvl
|
self.tcp_keepintvl = tcp_keepintvl
|
||||||
|
|
||||||
self.auto_pong = auto_pong
|
|
||||||
# Make paths settings absolute
|
# Make paths settings absolute
|
||||||
self.cert = os.path.abspath(cert)
|
self.cert = os.path.abspath(cert)
|
||||||
self.key = self.web = self.record = ''
|
self.key = self.web = self.record = ''
|
||||||
@@ -698,7 +666,7 @@ class WebSocketServer(object):
|
|||||||
raise Exception("SSL only supported in connect mode (for now)")
|
raise Exception("SSL only supported in connect mode (for now)")
|
||||||
if not connect:
|
if not connect:
|
||||||
flags = flags | socket.AI_PASSIVE
|
flags = flags | socket.AI_PASSIVE
|
||||||
|
|
||||||
if not unix_socket:
|
if not unix_socket:
|
||||||
addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
|
addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
|
||||||
socket.IPPROTO_TCP, flags)
|
socket.IPPROTO_TCP, flags)
|
||||||
@@ -729,7 +697,7 @@ class WebSocketServer(object):
|
|||||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
sock.bind(addrs[0][4])
|
sock.bind(addrs[0][4])
|
||||||
sock.listen(100)
|
sock.listen(100)
|
||||||
else:
|
else:
|
||||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
sock.connect(unix_socket)
|
sock.connect(unix_socket)
|
||||||
|
|
||||||
@@ -797,7 +765,7 @@ class WebSocketServer(object):
|
|||||||
handshake = sock.recv(1024, socket.MSG_PEEK)
|
handshake = sock.recv(1024, socket.MSG_PEEK)
|
||||||
#self.msg("Handshake [%s]" % handshake)
|
#self.msg("Handshake [%s]" % handshake)
|
||||||
|
|
||||||
if not handshake:
|
if handshake == "":
|
||||||
raise self.EClose("ignoring empty handshake")
|
raise self.EClose("ignoring empty handshake")
|
||||||
|
|
||||||
elif handshake.startswith(s2b("<policy-file-request/>")):
|
elif handshake.startswith(s2b("<policy-file-request/>")):
|
||||||
@@ -880,7 +848,7 @@ class WebSocketServer(object):
|
|||||||
raise self.Terminate()
|
raise self.Terminate()
|
||||||
|
|
||||||
def multiprocessing_SIGCHLD(self, sig, stack):
|
def multiprocessing_SIGCHLD(self, sig, stack):
|
||||||
self.vmsg('Reaping zombies, active child count is %s', len(multiprocessing.active_children()))
|
self.vmsg('Reaing zombies, active child count is %s', len(multiprocessing.active_children()))
|
||||||
|
|
||||||
def fallback_SIGCHLD(self, sig, stack):
|
def fallback_SIGCHLD(self, sig, stack):
|
||||||
# Reap zombies when using os.fork() (python 2.4)
|
# Reap zombies when using os.fork() (python 2.4)
|
||||||
|
|||||||
+55
-137
@@ -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, errno
|
import signal, socket, optparse, time, os, sys, subprocess, logging
|
||||||
try: from socketserver import ForkingMixIn
|
try: from socketserver import ForkingMixIn
|
||||||
except: from SocketServer import ForkingMixIn
|
except: from SocketServer import ForkingMixIn
|
||||||
try: from http.server import HTTPServer
|
try: from http.server import HTTPServer
|
||||||
except: from BaseHTTPServer import HTTPServer
|
except: from BaseHTTPServer import HTTPServer
|
||||||
import select
|
from select import select
|
||||||
from websockify import websocket
|
import websocket
|
||||||
try:
|
try:
|
||||||
from urllib.parse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
except:
|
except:
|
||||||
@@ -44,13 +44,8 @@ Traffic Legend:
|
|||||||
"""
|
"""
|
||||||
# Checks if we receive a token, and look
|
# Checks if we receive a token, and look
|
||||||
# for a valid target for it then
|
# for a valid target for it then
|
||||||
if self.server.token_plugin:
|
if self.server.target_cfg:
|
||||||
(self.server.target_host, self.server.target_port) = self.get_target(self.server.token_plugin, self.path)
|
(self.server.target_host, self.server.target_port) = self.get_target(self.server.target_cfg, 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
|
# Connect to the target
|
||||||
if self.server.wrap_cmd:
|
if self.server.wrap_cmd:
|
||||||
@@ -78,15 +73,15 @@ Traffic Legend:
|
|||||||
if tsock:
|
if tsock:
|
||||||
tsock.shutdown(socket.SHUT_RDWR)
|
tsock.shutdown(socket.SHUT_RDWR)
|
||||||
tsock.close()
|
tsock.close()
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
self.log_message("%s:%s: Closed target",
|
self.log_message("%s:%s: Closed target" %(
|
||||||
self.server.target_host, self.server.target_port)
|
self.server.target_host, self.server.target_port))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def get_target(self, target_plugin, path):
|
def get_target(self, target_cfg, path):
|
||||||
"""
|
"""
|
||||||
Parses the path, extracts a token, and looks up a target
|
Parses the path, extracts a token, and looks for a valid
|
||||||
for that token using the token plugin. Sets
|
target for that token in the configuration file(s). Sets
|
||||||
target_host and target_port if successful
|
target_host and target_port if successful
|
||||||
"""
|
"""
|
||||||
# The files in targets contain the lines
|
# The files in targets contain the lines
|
||||||
@@ -95,17 +90,32 @@ Traffic Legend:
|
|||||||
# Extract the token parameter from url
|
# Extract the token parameter from url
|
||||||
args = parse_qs(urlparse(path)[4]) # 4 is the query from url
|
args = parse_qs(urlparse(path)[4]) # 4 is the query from url
|
||||||
|
|
||||||
if not 'token' in args or not len(args['token']):
|
if not args.has_key('token') or not len(args['token']):
|
||||||
raise self.server.EClose("Token not present")
|
raise self.EClose("Token not present")
|
||||||
|
|
||||||
token = args['token'][0].rstrip('\n')
|
token = args['token'][0].rstrip('\n')
|
||||||
|
|
||||||
result_pair = target_plugin.lookup(token)
|
# target_cfg can be a single config file or directory of
|
||||||
|
# config files
|
||||||
if result_pair is not None:
|
if os.path.isdir(target_cfg):
|
||||||
return result_pair
|
cfg_files = [os.path.join(target_cfg, f)
|
||||||
|
for f in os.listdir(target_cfg)]
|
||||||
else:
|
else:
|
||||||
raise self.server.EClose("Token '%s' not found" % token)
|
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)
|
||||||
|
|
||||||
def do_proxy(self, target):
|
def do_proxy(self, target):
|
||||||
"""
|
"""
|
||||||
@@ -116,37 +126,12 @@ Traffic Legend:
|
|||||||
tqueue = []
|
tqueue = []
|
||||||
rlist = [self.request, target]
|
rlist = [self.request, target]
|
||||||
|
|
||||||
if self.server.heartbeat:
|
|
||||||
now = time.time()
|
|
||||||
self.heartbeat = now + self.server.heartbeat
|
|
||||||
else:
|
|
||||||
self.heartbeat = None
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
wlist = []
|
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 tqueue: wlist.append(target)
|
||||||
if cqueue or c_pend: wlist.append(self.request)
|
if cqueue or c_pend: wlist.append(self.request)
|
||||||
try:
|
ins, outs, excepts = select(rlist, wlist, [], 1)
|
||||||
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 excepts: raise Exception("Socket exception")
|
||||||
|
|
||||||
if self.request in outs:
|
if self.request in outs:
|
||||||
@@ -162,9 +147,9 @@ Traffic Legend:
|
|||||||
|
|
||||||
if closed:
|
if closed:
|
||||||
# TODO: What about blocking on client socket?
|
# TODO: What about blocking on client socket?
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
self.log_message("%s:%s: Client closed connection",
|
self.log_message("%s:%s: Client closed connection" %(
|
||||||
self.server.target_host, self.server.target_port)
|
self.server.target_host, self.server.target_port))
|
||||||
raise self.CClose(closed['code'], closed['reason'])
|
raise self.CClose(closed['code'], closed['reason'])
|
||||||
|
|
||||||
|
|
||||||
@@ -185,8 +170,8 @@ Traffic Legend:
|
|||||||
buf = target.recv(self.buffer_size)
|
buf = target.recv(self.buffer_size)
|
||||||
if len(buf) == 0:
|
if len(buf) == 0:
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
self.log_message("%s:%s: Target closed connection",
|
self.log_message("%s:%s: Target closed connection" %(
|
||||||
self.server.target_host, self.server.target_port)
|
self.server.target_host, self.server.target_port))
|
||||||
raise self.CClose(1000, "Target closed")
|
raise self.CClose(1000, "Target closed")
|
||||||
|
|
||||||
cqueue.append(buf)
|
cqueue.append(buf)
|
||||||
@@ -210,11 +195,7 @@ class WebSocketProxy(websocket.WebSocketServer):
|
|||||||
self.wrap_mode = kwargs.pop('wrap_mode', None)
|
self.wrap_mode = kwargs.pop('wrap_mode', None)
|
||||||
self.unix_target = kwargs.pop('unix_target', None)
|
self.unix_target = kwargs.pop('unix_target', None)
|
||||||
self.ssl_target = kwargs.pop('ssl_target', None)
|
self.ssl_target = kwargs.pop('ssl_target', None)
|
||||||
self.heartbeat = kwargs.pop('heartbeat', 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)
|
|
||||||
|
|
||||||
# Last 3 timestamps command was run
|
# Last 3 timestamps command was run
|
||||||
self.wrap_times = [0, 0, 0]
|
self.wrap_times = [0, 0, 0]
|
||||||
|
|
||||||
@@ -270,9 +251,9 @@ class WebSocketProxy(websocket.WebSocketServer):
|
|||||||
else:
|
else:
|
||||||
dst_string = "%s:%s" % (self.target_host, self.target_port)
|
dst_string = "%s:%s" % (self.target_host, self.target_port)
|
||||||
|
|
||||||
if self.token_plugin:
|
if self.target_cfg:
|
||||||
msg = " - proxying from %s:%s to targets generated by %s" % (
|
msg = " - proxying from %s:%s to targets in %s" % (
|
||||||
self.listen_host, self.listen_port, type(self.token_plugin).__name__)
|
self.listen_host, self.listen_port, self.target_cfg)
|
||||||
else:
|
else:
|
||||||
msg = " - proxying from %s:%s to %s" % (
|
msg = " - proxying from %s:%s to %s" % (
|
||||||
self.listen_host, self.listen_port, dst_string)
|
self.listen_host, self.listen_port, dst_string)
|
||||||
@@ -371,56 +352,20 @@ def websockify_init():
|
|||||||
parser.add_option("--prefer-ipv6", "-6",
|
parser.add_option("--prefer-ipv6", "-6",
|
||||||
action="store_true", dest="source_is_ipv6",
|
action="store_true", dest="source_is_ipv6",
|
||||||
help="prefer IPv6 when resolving source_addr")
|
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",
|
parser.add_option("--target-config", metavar="FILE",
|
||||||
dest="target_cfg",
|
dest="target_cfg",
|
||||||
help="Configuration file containing valid targets "
|
help="Configuration file containing valid targets "
|
||||||
"in the form 'token: host:port' or, alternatively, a "
|
"in the form 'token: host:port' or, alternatively, a "
|
||||||
"directory containing configuration files of this form "
|
"directory containing configuration files of this form")
|
||||||
"(DEPRECATED: use `--token-plugin TokenFile --token-source "
|
parser.add_option("--libserver", action="store_true",
|
||||||
" path/to/token/file` instead)")
|
help="use Python library SocketServer engine")
|
||||||
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()
|
(opts, args) = parser.parse_args()
|
||||||
|
|
||||||
if opts.verbose:
|
if opts.verbose:
|
||||||
logging.getLogger(WebSocketProxy.log_prefix).setLevel(logging.DEBUG)
|
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
|
# Sanity checks
|
||||||
if len(args) < 2 and not (opts.token_plugin or opts.unix_target):
|
if len(args) < 2 and not (opts.target_cfg or opts.unix_target):
|
||||||
parser.error("Too few arguments")
|
parser.error("Too few arguments")
|
||||||
if sys.argv.count('--'):
|
if sys.argv.count('--'):
|
||||||
opts.wrap_cmd = args[1:]
|
opts.wrap_cmd = args[1:]
|
||||||
@@ -445,7 +390,7 @@ def websockify_init():
|
|||||||
try: opts.listen_port = int(opts.listen_port)
|
try: opts.listen_port = int(opts.listen_port)
|
||||||
except: parser.error("Error parsing listen port")
|
except: parser.error("Error parsing listen port")
|
||||||
|
|
||||||
if opts.wrap_cmd or opts.unix_target or opts.token_plugin:
|
if opts.wrap_cmd or opts.unix_target or opts.target_cfg:
|
||||||
opts.target_host = None
|
opts.target_host = None
|
||||||
opts.target_port = None
|
opts.target_port = None
|
||||||
else:
|
else:
|
||||||
@@ -457,32 +402,9 @@ def websockify_init():
|
|||||||
try: opts.target_port = int(opts.target_port)
|
try: opts.target_port = int(opts.target_port)
|
||||||
except: parser.error("Error parsing target port")
|
except: parser.error("Error parsing target port")
|
||||||
|
|
||||||
if opts.token_plugin is not None:
|
# Transform to absolute path as daemon may chdir
|
||||||
if '.' not in opts.token_plugin:
|
if opts.target_cfg:
|
||||||
opts.token_plugin = (
|
opts.target_cfg = os.path.abspath(opts.target_cfg)
|
||||||
'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
|
# Create and start the WebSockets proxy
|
||||||
libserver = opts.libserver
|
libserver = opts.libserver
|
||||||
@@ -511,13 +433,9 @@ class LibProxyServer(ForkingMixIn, HTTPServer):
|
|||||||
self.wrap_mode = kwargs.pop('wrap_mode', None)
|
self.wrap_mode = kwargs.pop('wrap_mode', None)
|
||||||
self.unix_target = kwargs.pop('unix_target', None)
|
self.unix_target = kwargs.pop('unix_target', None)
|
||||||
self.ssl_target = kwargs.pop('ssl_target', None)
|
self.ssl_target = kwargs.pop('ssl_target', None)
|
||||||
self.token_plugin = kwargs.pop('token_plugin', None)
|
self.target_cfg = kwargs.pop('target_cfg', 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.daemon = False
|
||||||
|
self.target_cfg = None
|
||||||
|
|
||||||
# Server configuration
|
# Server configuration
|
||||||
listen_host = kwargs.pop('listen_host', '')
|
listen_host = kwargs.pop('listen_host', '')
|
||||||
@@ -538,8 +456,8 @@ class LibProxyServer(ForkingMixIn, HTTPServer):
|
|||||||
|
|
||||||
if web:
|
if web:
|
||||||
os.chdir(web)
|
os.chdir(web)
|
||||||
|
|
||||||
HTTPServer.__init__(self, (listen_host, listen_port),
|
HTTPServer.__init__(self, (listen_host, listen_port),
|
||||||
RequestHandlerClass)
|
RequestHandlerClass)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user