Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ee9ff7f4d |
+1
-3
@@ -4,10 +4,8 @@
|
|||||||
other/.lein-deps-sum
|
other/.lein-deps-sum
|
||||||
other/classes
|
other/classes
|
||||||
other/lib
|
other/lib
|
||||||
other/js/node_modules
|
other/node_modules
|
||||||
.project
|
.project
|
||||||
.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
|
|
||||||
-37
@@ -1,43 +1,6 @@
|
|||||||
Changes
|
Changes
|
||||||
=======
|
=======
|
||||||
|
|
||||||
0.8.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Make websockify properly terminate children on SIGTERM (#226)
|
|
||||||
* Remove logging in signal handlers (this can cause Python to hang under certain conditions) (#219)
|
|
||||||
* Make it easier to log to a file (#205)
|
|
||||||
* Add support for IPv6 addresses in tokens in the TokenFile token plugins (#197)
|
|
||||||
* Improve auth plugin framework to enable better support for HTTP auth (#194, #201)
|
|
||||||
* Fix bug in JSONTokenAPI token plugin (#192)
|
|
||||||
* Fix a missing variable in the exception handler (#178)
|
|
||||||
|
|
||||||
0.7.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Python 3 support fixes (#140, #155, #159)
|
|
||||||
* Generic token-parsing plugins support (#162)
|
|
||||||
* Generic authentication plugins support (#172)
|
|
||||||
* Fixed frame corruption on big-endian systems (#161)
|
|
||||||
* Support heartbeats (via PING) and automatic responses to PONG (#169)
|
|
||||||
* Automatically reject unmasked client frames by default (strict mode) (#174)
|
|
||||||
* Automatically restart interrupted select calls (#175)
|
|
||||||
* Make 'run' respect environment settings (including virtualenv) (#176)
|
|
||||||
|
|
||||||
0.6.1 - May 11, 2015
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
* **PATCH RELEASE**: Fixes a bug causing file_only to not be passed properly
|
|
||||||
|
|
||||||
0.6.0 - Feb 18, 2014
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
* **NOTE** : 0.6.0 will break existing code that sub-classes WebsocketProxy
|
|
||||||
* Refactor to use standard SocketServer RequestHandler design
|
|
||||||
* Fix zombie process bug on certain systems when using multiprocessing
|
|
||||||
* Add better unit tests
|
|
||||||
* Log information via python `logging` module
|
|
||||||
|
|
||||||
0.5.1 - Jun 27, 2013
|
0.5.1 - Jun 27, 2013
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|||||||
@@ -8,28 +8,6 @@ to normal socket traffic. Websockify accepts the WebSockets handshake,
|
|||||||
parses it, and then begins forwarding traffic between the client and
|
parses it, and then begins forwarding traffic between the client and
|
||||||
the target in both directions.
|
the target in both directions.
|
||||||
|
|
||||||
### News/help/contact
|
|
||||||
|
|
||||||
Notable commits, announcements and news are posted to
|
|
||||||
<a href="http://www.twitter.com/noVNC">@noVNC</a>
|
|
||||||
|
|
||||||
If you are a websockify developer/integrator/user (or want to be)
|
|
||||||
please join the <a
|
|
||||||
href="https://groups.google.com/forum/?fromgroups#!forum/novnc">noVNC/websockify
|
|
||||||
discussion group</a>
|
|
||||||
|
|
||||||
Bugs and feature requests can be submitted via [github
|
|
||||||
issues](https://github.com/kanaka/websockify/issues).
|
|
||||||
|
|
||||||
If you want to show appreciation for websockify you could donate to a great
|
|
||||||
non-profits such as: [Compassion
|
|
||||||
International](http://www.compassion.com/), [SIL](http://www.sil.org),
|
|
||||||
[Habitat for Humanity](http://www.habitat.org), [Electronic Frontier
|
|
||||||
Foundation](https://www.eff.org/), [Against Malaria
|
|
||||||
Foundation](http://www.againstmalaria.com/), [Nothing But
|
|
||||||
Nets](http://www.nothingbutnets.net/), etc. Please tweet <a
|
|
||||||
href="http://www.twitter.com/noVNC">@noVNC</a> if you do.
|
|
||||||
|
|
||||||
### WebSockets binary data
|
### WebSockets binary data
|
||||||
|
|
||||||
Starting with websockify 0.5.0, only the HyBi / IETF
|
Starting with websockify 0.5.0, only the HyBi / IETF
|
||||||
@@ -47,29 +25,17 @@ which is why the negotiation is necessary.
|
|||||||
|
|
||||||
### Encrypted WebSocket connections (wss://)
|
### Encrypted WebSocket connections (wss://)
|
||||||
|
|
||||||
To encrypt the traffic using the WebSocket 'wss://' URI scheme you need to
|
To encrypt the traffic using the WebSocket 'wss://' URI scheme you
|
||||||
generate a certificate and key for Websockify to load. By default, Websockify
|
need to generate a certificate for websockify to load. By default websockify
|
||||||
loads a certificate file name `self.pem` but the `--cert=CERT` and `--key=KEY`
|
loads a certificate file name `self.pem` but the `--cert=CERT` option can
|
||||||
options can override the file name. You can generate a self-signed certificate
|
override the file name. You can generate a self-signed certificate using
|
||||||
using openssl. When asked for the common name, use the hostname of the server
|
openssl. When asked for the common name, use the hostname of the server where
|
||||||
where the proxy will be running:
|
the proxy will be running:
|
||||||
|
|
||||||
```
|
```
|
||||||
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
|
||||||
```
|
```
|
||||||
|
|
||||||
For a self-signed certificate to work, you need to make your client/browser
|
|
||||||
understand it. You can do this by installing it as accepted certificate, or by
|
|
||||||
using that same certificate for a HTTPS connection to which you navigate first
|
|
||||||
and approve. Browsers generally don't give you the "trust certificate?" prompt
|
|
||||||
by opening a WSS socket with invalid certificate, hence you need to have it
|
|
||||||
acccept it by either of those two methods.
|
|
||||||
|
|
||||||
If you have a commercial/valid SSL certificate with one ore more intermediate
|
|
||||||
certificates, concat them into one file, server certificate first, then the
|
|
||||||
intermediate(s) from the CA, etc. Point to this file with the `--cert` option
|
|
||||||
and then also to the key with `--key`. Finally, use `--ssl-only` as needed.
|
|
||||||
|
|
||||||
|
|
||||||
### Websock Javascript library
|
### Websock Javascript library
|
||||||
|
|
||||||
@@ -115,14 +81,11 @@ These are not necessary for the basic operation.
|
|||||||
|
|
||||||
* Mini-webserver: websockify can detect and respond to normal web
|
* Mini-webserver: websockify can detect and respond to normal web
|
||||||
requests on the same port as the WebSockets proxy and Flash security
|
requests on the same port as the WebSockets proxy and Flash security
|
||||||
policy. This functionality is activated with the `--web DIR` option
|
policy. This functionality is activate with the `--web DIR` option
|
||||||
where DIR is the root of the web directory to serve.
|
where DIR is the root of the web directory to serve.
|
||||||
|
|
||||||
* Wrap a program: see the "Wrap a Program" section below.
|
* Wrap a program: see the "Wrap a Program" section below.
|
||||||
|
|
||||||
* Log files: websockify can save all logging information in a file.
|
|
||||||
This functionality is activated with the `--log-file FILE` option
|
|
||||||
where FILE is the file where the logs should be saved.
|
|
||||||
|
|
||||||
### Implementations of websockify
|
### Implementations of websockify
|
||||||
|
|
||||||
|
|||||||
+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 = {};
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
"author": "Joel Martin <github@martintribe.org> (http://github.com/kanaka)",
|
"author": "Joel Martin <github@martintribe.org> (http://github.com/kanaka)",
|
||||||
"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.0",
|
"license": "LGPL-3",
|
||||||
"version": "0.8.0",
|
"version": "0.5.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"
|
||||||
},
|
},
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ws": ">=0.4.27",
|
"ws": ">=0.4.27",
|
||||||
|
"base64": "latest",
|
||||||
"optimist": "latest",
|
"optimist": "latest",
|
||||||
"policyfile": "latest"
|
"policyfile": "latest"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.8.0'
|
version = '0.5.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
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
""" Unit tests for Authentication plugins"""
|
|
||||||
|
|
||||||
from websockify.auth_plugins import BasicHTTPAuth, AuthenticationError
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
|
|
||||||
class BasicHTTPAuthTestCase(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.plugin = BasicHTTPAuth('Aladdin:open sesame')
|
|
||||||
|
|
||||||
def test_no_auth(self):
|
|
||||||
headers = {}
|
|
||||||
self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234')
|
|
||||||
|
|
||||||
def test_invalid_password(self):
|
|
||||||
headers = {'Authorization': 'Basic QWxhZGRpbjpzZXNhbWUgc3RyZWV0'}
|
|
||||||
self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234')
|
|
||||||
|
|
||||||
def test_valid_password(self):
|
|
||||||
headers = {'Authorization': 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='}
|
|
||||||
self.plugin.authenticate(headers, 'localhost', '1234')
|
|
||||||
|
|
||||||
def test_garbage_auth(self):
|
|
||||||
headers = {'Authorization': 'Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxx'}
|
|
||||||
self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234')
|
|
||||||
+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, 'send_auth_error',
|
self.stubs.Set(web_socket_proxy, 'run_wrap_cmd', mock_run_wrap_cmd)
|
||||||
staticmethod(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.validate_connection()
|
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(response_msg="some_error")
|
|
||||||
|
|
||||||
self.stubs.Set(websocketproxy.ProxyRequestHandler, 'send_auth_error',
|
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.validate_connection)
|
|
||||||
|
|
||||||
self.handler.server.target_host = "someotherhost"
|
|
||||||
self.handler.validate_connection()
|
|
||||||
|
|
||||||
|
self.stubs.Set(select, 'select', mock_select)
|
||||||
|
self.assertRaises(Exception, web_socket_proxy.new_websocket_client)
|
||||||
|
|||||||
@@ -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,83 +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):
|
|
||||||
def __init__(self, log_msg=None, response_code=403, response_headers={}, response_msg=None):
|
|
||||||
self.code = response_code
|
|
||||||
self.headers = response_headers
|
|
||||||
self.msg = response_msg
|
|
||||||
|
|
||||||
if log_msg is None:
|
|
||||||
log_msg = response_msg
|
|
||||||
|
|
||||||
super(AuthenticationError, self).__init__('%s %s' % (self.code, log_msg))
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidOriginError(AuthenticationError):
|
|
||||||
def __init__(self, expected, actual):
|
|
||||||
self.expected_origin = expected
|
|
||||||
self.actual_origin = actual
|
|
||||||
|
|
||||||
super(InvalidOriginError, self).__init__(
|
|
||||||
response_msg='Invalid Origin',
|
|
||||||
log_msg="Invalid Origin Header: Expected one of "
|
|
||||||
"%s, got '%s'" % (expected, actual))
|
|
||||||
|
|
||||||
|
|
||||||
class BasicHTTPAuth(object):
|
|
||||||
"""Verifies Basic Auth headers. Specify src as username:password"""
|
|
||||||
|
|
||||||
def __init__(self, src=None):
|
|
||||||
self.src = src
|
|
||||||
|
|
||||||
def authenticate(self, headers, target_host, target_port):
|
|
||||||
import base64
|
|
||||||
auth_header = headers.get('Authorization')
|
|
||||||
if auth_header:
|
|
||||||
if not auth_header.startswith('Basic '):
|
|
||||||
raise AuthenticationError(response_code=403)
|
|
||||||
|
|
||||||
try:
|
|
||||||
user_pass_raw = base64.b64decode(auth_header[6:])
|
|
||||||
except TypeError:
|
|
||||||
raise AuthenticationError(response_code=403)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# http://stackoverflow.com/questions/7242316/what-encoding-should-i-use-for-http-basic-authentication
|
|
||||||
user_pass_as_text = user_pass_raw.decode('ISO-8859-1')
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
raise AuthenticationError(response_code=403)
|
|
||||||
|
|
||||||
user_pass = user_pass_as_text.split(':', 1)
|
|
||||||
if len(user_pass) != 2:
|
|
||||||
raise AuthenticationError(response_code=403)
|
|
||||||
|
|
||||||
if not self.validate_creds(*user_pass):
|
|
||||||
raise AuthenticationError(response_code=403)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise AuthenticationError(response_code=401,
|
|
||||||
response_headers={'WWW-Authenticate': 'Basic realm="Websockify"'})
|
|
||||||
|
|
||||||
def validate_creds(self, username, password):
|
|
||||||
if '%s:%s' % (username, password) == self.src:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
class ExpectOrigin(object):
|
|
||||||
def __init__(self, src=None):
|
|
||||||
if src is None:
|
|
||||||
self.source = []
|
|
||||||
else:
|
|
||||||
self.source = src.split()
|
|
||||||
|
|
||||||
def authenticate(self, headers, target_host, target_port):
|
|
||||||
origin = headers.get('Origin', None)
|
|
||||||
if origin is None or origin not in self.source:
|
|
||||||
raise InvalidOriginError(expected=self.source, actual=origin)
|
|
||||||
@@ -1,83 +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().rsplit(':', 1)
|
|
||||||
|
|
||||||
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):
|
|
||||||
resp_json = resp.json()
|
|
||||||
return (resp_json['host'], resp_json['port'])
|
|
||||||
+53
-128
@@ -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,18 +104,13 @@ 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)
|
||||||
|
|
||||||
def log_message(self, format, *args):
|
|
||||||
self.logger.info("%s - - [%s] %s" % (self.address_string(), self.log_date_time_string(), format % args))
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def unmask(buf, hlen, plen):
|
def unmask(buf, hlen, plen):
|
||||||
pstart = hlen + 4
|
pstart = hlen + 4
|
||||||
@@ -123,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:
|
||||||
@@ -181,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,
|
||||||
@@ -247,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]:
|
||||||
@@ -359,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:
|
||||||
@@ -374,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("}")
|
||||||
|
|
||||||
@@ -411,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
|
||||||
|
|
||||||
@@ -468,22 +435,18 @@ 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'):
|
||||||
|
|
||||||
# ensure connection is authorized, and determine the target
|
|
||||||
self.validate_connection()
|
|
||||||
|
|
||||||
if not self.do_websocket_handshake():
|
if not self.do_websocket_handshake():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -505,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"
|
||||||
@@ -532,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
|
||||||
@@ -551,15 +514,11 @@ 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")
|
||||||
|
|
||||||
def validate_connection(self):
|
|
||||||
""" Ensure that the connection is a valid connection, and set the target. """
|
|
||||||
pass
|
|
||||||
|
|
||||||
def do_HEAD(self):
|
def do_HEAD(self):
|
||||||
if self.only_upgrade:
|
if self.only_upgrade:
|
||||||
self.send_error(405, "Method Not Allowed")
|
self.send_error(405, "Method Not Allowed")
|
||||||
@@ -601,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
|
||||||
@@ -622,9 +581,7 @@ class WebSocketServer(object):
|
|||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.idle_timeout = idle_timeout
|
self.idle_timeout = idle_timeout
|
||||||
self.traffic = traffic
|
self.traffic = traffic
|
||||||
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
|
||||||
self.handler_id = 1
|
self.handler_id = 1
|
||||||
@@ -635,7 +592,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 = ''
|
||||||
@@ -662,10 +618,7 @@ class WebSocketServer(object):
|
|||||||
self.listen_host, self.listen_port)
|
self.listen_host, self.listen_port)
|
||||||
self.msg(" - Flash security policy server")
|
self.msg(" - Flash security policy server")
|
||||||
if self.web:
|
if self.web:
|
||||||
if self.file_only:
|
self.msg(" - Web server. Web root: %s", self.web)
|
||||||
self.msg(" - Web server (no directory listings). Web root: %s", self.web)
|
|
||||||
else:
|
|
||||||
self.msg(" - Web server. Web root: %s", self.web)
|
|
||||||
if ssl:
|
if ssl:
|
||||||
if os.path.exists(self.cert):
|
if os.path.exists(self.cert):
|
||||||
self.msg(" - SSL/TLS support")
|
self.msg(" - SSL/TLS support")
|
||||||
@@ -709,7 +662,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)
|
||||||
@@ -740,7 +693,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)
|
||||||
|
|
||||||
@@ -748,10 +701,6 @@ class WebSocketServer(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def daemonize(keepfd=None, chdir='/'):
|
def daemonize(keepfd=None, chdir='/'):
|
||||||
|
|
||||||
if keepfd is None:
|
|
||||||
keepfd = []
|
|
||||||
|
|
||||||
os.umask(0)
|
os.umask(0)
|
||||||
if chdir:
|
if chdir:
|
||||||
os.chdir(chdir)
|
os.chdir(chdir)
|
||||||
@@ -774,7 +723,7 @@ class WebSocketServer(object):
|
|||||||
if maxfd == resource.RLIM_INFINITY: maxfd = 256
|
if maxfd == resource.RLIM_INFINITY: maxfd = 256
|
||||||
for fd in reversed(range(maxfd)):
|
for fd in reversed(range(maxfd)):
|
||||||
try:
|
try:
|
||||||
if fd not in keepfd:
|
if fd != keepfd:
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
except OSError:
|
except OSError:
|
||||||
_, exc, _ = sys.exc_info()
|
_, exc, _ = sys.exc_info()
|
||||||
@@ -804,7 +753,7 @@ class WebSocketServer(object):
|
|||||||
"""
|
"""
|
||||||
ready = select.select([sock], [], [], 3)[0]
|
ready = select.select([sock], [], [], 3)[0]
|
||||||
|
|
||||||
|
|
||||||
if not ready:
|
if not ready:
|
||||||
raise self.EClose("ignoring socket not ready")
|
raise self.EClose("ignoring socket not ready")
|
||||||
# Peek, but do not read the data so that we have a opportunity
|
# Peek, but do not read the data so that we have a opportunity
|
||||||
@@ -812,7 +761,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/>")):
|
||||||
@@ -895,14 +844,11 @@ class WebSocketServer(object):
|
|||||||
raise self.Terminate()
|
raise self.Terminate()
|
||||||
|
|
||||||
def multiprocessing_SIGCHLD(self, sig, stack):
|
def multiprocessing_SIGCHLD(self, sig, stack):
|
||||||
# TODO: figure out a way to actually log this information without
|
self.vmsg('Reaing zombies, active child count is %s', len(multiprocessing.active_children()))
|
||||||
# calling `log` in the signal handlers
|
|
||||||
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)
|
||||||
# TODO: figure out a way to actually log this information without
|
self.vmsg("Got SIGCHLD, reaping zombies")
|
||||||
# calling `log` in the signal handlers
|
|
||||||
try:
|
try:
|
||||||
result = os.waitpid(-1, os.WNOHANG)
|
result = os.waitpid(-1, os.WNOHANG)
|
||||||
while result[0]:
|
while result[0]:
|
||||||
@@ -912,18 +858,16 @@ class WebSocketServer(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def do_SIGINT(self, sig, stack):
|
def do_SIGINT(self, sig, stack):
|
||||||
# TODO: figure out a way to actually log this information without
|
self.msg("Got SIGINT, exiting")
|
||||||
# calling `log` in the signal handlers
|
|
||||||
self.terminate()
|
self.terminate()
|
||||||
|
|
||||||
def do_SIGTERM(self, sig, stack):
|
def do_SIGTERM(self, sig, stack):
|
||||||
# TODO: figure out a way to actually log this information without
|
self.msg("Got SIGTERM, exiting")
|
||||||
# calling `log` in the signal handlers
|
|
||||||
self.terminate()
|
self.terminate()
|
||||||
|
|
||||||
def top_new_client(self, startsock, address):
|
def top_new_client(self, startsock, address):
|
||||||
""" Do something with a WebSockets client connection. """
|
""" Do something with a WebSockets client connection. """
|
||||||
# handler process
|
# handler process
|
||||||
client = None
|
client = None
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
@@ -946,18 +890,6 @@ class WebSocketServer(object):
|
|||||||
# Original socket closed by caller
|
# Original socket closed by caller
|
||||||
client.close()
|
client.close()
|
||||||
|
|
||||||
def get_log_fd(self):
|
|
||||||
"""
|
|
||||||
Get file descriptors for the loggers.
|
|
||||||
They should not be closed when the process is forked.
|
|
||||||
"""
|
|
||||||
descriptors = []
|
|
||||||
for handler in self.logger.parent.handlers:
|
|
||||||
if isinstance(handler, logging.FileHandler):
|
|
||||||
descriptors.append(handler.stream.fileno())
|
|
||||||
|
|
||||||
return descriptors
|
|
||||||
|
|
||||||
def start_server(self):
|
def start_server(self):
|
||||||
"""
|
"""
|
||||||
Daemonize if requested. Listen for for connections. Run
|
Daemonize if requested. Listen for for connections. Run
|
||||||
@@ -973,9 +905,7 @@ class WebSocketServer(object):
|
|||||||
tcp_keepintvl=self.tcp_keepintvl)
|
tcp_keepintvl=self.tcp_keepintvl)
|
||||||
|
|
||||||
if self.daemon:
|
if self.daemon:
|
||||||
keepfd = self.get_log_fd()
|
self.daemonize(keepfd=lsock.fileno(), chdir=self.web)
|
||||||
keepfd.append(lsock.fileno())
|
|
||||||
self.daemonize(keepfd=keepfd, chdir=self.web)
|
|
||||||
|
|
||||||
self.started() # Some things need to happen after daemonizing
|
self.started() # Some things need to happen after daemonizing
|
||||||
|
|
||||||
@@ -983,17 +913,21 @@ class WebSocketServer(object):
|
|||||||
original_signals = {
|
original_signals = {
|
||||||
signal.SIGINT: signal.getsignal(signal.SIGINT),
|
signal.SIGINT: signal.getsignal(signal.SIGINT),
|
||||||
signal.SIGTERM: signal.getsignal(signal.SIGTERM),
|
signal.SIGTERM: signal.getsignal(signal.SIGTERM),
|
||||||
signal.SIGCHLD: signal.getsignal(signal.SIGCHLD),
|
|
||||||
}
|
}
|
||||||
|
if getattr(signal, 'SIGCHLD', None) is not None:
|
||||||
|
original_signals[signal.SIGCHLD] = signal.getsignal(signal.SIGCHLD),
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, self.do_SIGINT)
|
signal.signal(signal.SIGINT, self.do_SIGINT)
|
||||||
signal.signal(signal.SIGTERM, self.do_SIGTERM)
|
signal.signal(signal.SIGTERM, self.do_SIGTERM)
|
||||||
if not multiprocessing:
|
|
||||||
# os.fork() (python 2.4) child reaper
|
if getattr(signal, 'SIGCHLD', None) is not None:
|
||||||
signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
|
if not multiprocessing:
|
||||||
else:
|
# os.fork() (python 2.4) child reaper
|
||||||
# make sure that _cleanup is called when children die
|
signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
|
||||||
# by calling active_children on SIGCHLD
|
else:
|
||||||
signal.signal(signal.SIGCHLD, self.multiprocessing_SIGCHLD)
|
# make sure that _cleanup is called when children die
|
||||||
|
# by calling active_children on SIGCHLD
|
||||||
|
signal.signal(signal.SIGCHLD, self.multiprocessing_SIGCHLD)
|
||||||
|
|
||||||
last_active_time = self.launch_time
|
last_active_time = self.launch_time
|
||||||
try:
|
try:
|
||||||
@@ -1079,17 +1013,8 @@ class WebSocketServer(object):
|
|||||||
|
|
||||||
except (self.Terminate, SystemExit, KeyboardInterrupt):
|
except (self.Terminate, SystemExit, KeyboardInterrupt):
|
||||||
self.msg("In exit")
|
self.msg("In exit")
|
||||||
# terminate all child processes
|
|
||||||
if multiprocessing and not self.run_once:
|
|
||||||
children = multiprocessing.active_children()
|
|
||||||
|
|
||||||
for child in children:
|
|
||||||
self.msg("Terminating child %s" % child.pid)
|
|
||||||
child.terminate()
|
|
||||||
|
|
||||||
break
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
exc = sys.exc_info()[1]
|
|
||||||
self.msg("handler exception: %s", str(exc))
|
self.msg("handler exception: %s", str(exc))
|
||||||
self.vmsg("exception", exc_info=True)
|
self.vmsg("exception", exc_info=True)
|
||||||
|
|
||||||
|
|||||||
+57
-167
@@ -11,14 +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
|
||||||
from websockify import auth_plugins as auth
|
|
||||||
try:
|
try:
|
||||||
from urllib.parse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
except:
|
except:
|
||||||
@@ -38,34 +37,15 @@ Traffic Legend:
|
|||||||
< - Client send
|
< - Client send
|
||||||
<. - Client send partial
|
<. - Client send partial
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def send_auth_error(self, ex):
|
|
||||||
self.send_response(ex.code, ex.msg)
|
|
||||||
self.send_header('Content-Type', 'text/html')
|
|
||||||
for name, val in ex.headers.items():
|
|
||||||
self.send_header(name, val)
|
|
||||||
|
|
||||||
self.end_headers()
|
|
||||||
|
|
||||||
def validate_connection(self):
|
|
||||||
if self.server.token_plugin:
|
|
||||||
(self.server.target_host, self.server.target_port) = self.get_target(self.server.token_plugin, self.path)
|
|
||||||
|
|
||||||
if self.server.auth_plugin:
|
|
||||||
try:
|
|
||||||
self.server.auth_plugin.authenticate(
|
|
||||||
headers=self.headers, target_host=self.server.target_host,
|
|
||||||
target_port=self.server.target_port)
|
|
||||||
except auth.AuthenticationError:
|
|
||||||
ex = sys.exc_info()[1]
|
|
||||||
self.send_auth_error(ex)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def new_websocket_client(self):
|
def new_websocket_client(self):
|
||||||
"""
|
"""
|
||||||
Called after a new WebSocket connection has been established.
|
Called after a new WebSocket connection has been established.
|
||||||
"""
|
"""
|
||||||
# Checking for a token is done in validate_connection()
|
# 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)
|
||||||
|
|
||||||
# Connect to the target
|
# Connect to the target
|
||||||
if self.server.wrap_cmd:
|
if self.server.wrap_cmd:
|
||||||
@@ -93,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
|
||||||
@@ -110,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):
|
||||||
"""
|
"""
|
||||||
@@ -131,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:
|
||||||
@@ -177,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'])
|
||||||
|
|
||||||
|
|
||||||
@@ -200,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)
|
||||||
@@ -225,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]
|
||||||
|
|
||||||
@@ -285,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)
|
||||||
@@ -386,69 +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")
|
|
||||||
parser.add_option("--log-file", metavar="FILE",
|
|
||||||
dest="log_file",
|
|
||||||
help="File where logs will be saved")
|
|
||||||
|
|
||||||
|
|
||||||
(opts, args) = parser.parse_args()
|
(opts, args) = parser.parse_args()
|
||||||
|
|
||||||
if opts.log_file:
|
|
||||||
opts.log_file = os.path.abspath(opts.log_file)
|
|
||||||
handler = logging.FileHandler(opts.log_file)
|
|
||||||
handler.setLevel(logging.DEBUG)
|
|
||||||
handler.setFormatter(logging.Formatter("%(message)s"))
|
|
||||||
logging.getLogger(WebSocketProxy.log_prefix).addHandler(handler)
|
|
||||||
|
|
||||||
del opts.log_file
|
|
||||||
|
|
||||||
if opts.verbose:
|
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:]
|
||||||
@@ -473,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:
|
||||||
@@ -485,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
|
||||||
@@ -539,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', '')
|
||||||
@@ -566,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