Merge branch 'flake8' of github.com:kajinamit/websockify

This commit is contained in:
Pierre Ossman 2025-07-03 16:13:08 +02:00
commit bdf1ebf24e
19 changed files with 448 additions and 349 deletions

19
.github/workflows/lint.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update pip and setuptools
run: |
python -m pip install --upgrade pip
python -m pip install setuptools
- name: Install dependencies
run: |
python -m pip install flake8
- name: Lint with flake8
run: |
flake8

View File

@ -1,11 +1,12 @@
from setuptools import setup, find_packages
from setuptools import setup
version = '0.13.0'
name = 'websockify'
long_description = open("README.md").read() + "\n" + \
open("CHANGES.txt").read() + "\n"
setup(name=name,
setup(
name=name,
version=version,
description="Websockify.",
long_description=long_description,
@ -28,11 +29,11 @@ setup(name=name,
url="https://github.com/novnc/websockify",
author="Joel Martin",
author_email="github@martintribe.org",
packages=['websockify'],
include_package_data=True,
install_requires=[
'numpy', 'requests',
'numpy',
'requests',
'jwcrypto',
'redis',
],

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python
# flake8: noqa: E402
'''
A WebSocket server that echos back whatever it receives from the client.
Copyright 2010 Joel Martin
@ -10,9 +10,17 @@ openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
import os, sys, select, optparse, logging
import logging
import optparse
import os
import select
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from websockify.websockifyserver import WebSockifyServer, WebSockifyRequestHandler
from websockify.websockifyserver import WebSockifyServer
from websockify.websockifyserver import WebSockifyRequestHandler
class WebSocketEcho(WebSockifyRequestHandler):
"""
@ -27,15 +35,17 @@ class WebSocketEcho(WebSockifyRequestHandler):
cqueue = []
c_pend = 0
cpartial = ""
cpartial = "" # noqa: F841
rlist = [self.request]
while True:
wlist = []
if cqueue or c_pend: wlist.append(self.request)
if cqueue or c_pend:
wlist.append(self.request)
ins, outs, excepts = select.select(rlist, wlist, [], 1)
if excepts: raise Exception("Socket exception")
if excepts:
raise Exception("Socket exception")
if self.request in outs:
# Send queued target data to the client
@ -50,6 +60,7 @@ class WebSocketEcho(WebSockifyRequestHandler):
if closed:
break
if __name__ == '__main__':
parser = optparse.OptionParser(usage="%prog [options] listen_port")
parser.add_option("--verbose", "-v", action="store_true",
@ -63,7 +74,8 @@ if __name__ == '__main__':
(opts, args) = parser.parse_args()
try:
if len(args) != 1: raise ValueError
if len(args) != 1:
raise ValueError
opts.listen_port = int(args[0])
except ValueError:
parser.error("Invalid arguments")
@ -73,4 +85,3 @@ if __name__ == '__main__':
opts.web = "."
server = WebSockifyServer(WebSocketEcho, **opts.__dict__)
server.start_server()

View File

@ -1,13 +1,16 @@
#!/usr/bin/env python
# flake8: noqa: E402
import os
import sys
import optparse
import os
import select
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from websockify.websocket import WebSocket, \
WebSocketWantReadError, WebSocketWantWriteError
from websockify.websocket import WebSocket
from websockify.websocket import WebSocketWantReadError
from websockify.websocket import WebSocketWantWriteError
parser = optparse.OptionParser(usage="%prog URL")
(opts, args) = parser.parse_args()
@ -22,6 +25,7 @@ print("Connecting to %s..." % URL)
sock.connect(URL)
print("Connected.")
def send(msg):
while True:
try:
@ -30,11 +34,14 @@ def send(msg):
except WebSocketWantReadError:
msg = ''
ins, outs, excepts = select.select([sock], [], [])
if excepts: raise Exception("Socket exception")
if excepts:
raise Exception("Socket exception")
except WebSocketWantWriteError:
msg = ''
ins, outs, excepts = select.select([], [sock], [])
if excepts: raise Exception("Socket exception")
if excepts:
raise Exception("Socket exception")
def read():
while True:
@ -42,10 +49,13 @@ def read():
return sock.recvmsg()
except WebSocketWantReadError:
ins, outs, excepts = select.select([sock], [], [])
if excepts: raise Exception("Socket exception")
if excepts:
raise Exception("Socket exception")
except WebSocketWantWriteError:
ins, outs, excepts = select.select([], [sock], [])
if excepts: raise Exception("Socket exception")
if excepts:
raise Exception("Socket exception")
counter = 1
while True:
@ -56,7 +66,8 @@ while True:
while True:
ins, outs, excepts = select.select([sock], [], [], 1.0)
if excepts: raise Exception("Socket exception")
if excepts:
raise Exception("Socket exception")
if ins == []:
break

View File

@ -1,4 +1,5 @@
#!/usr/bin/env python
# flake8: noqa: E402
'''
WebSocket server-side load test program. Sends and receives traffic
@ -6,9 +7,19 @@ that has a random payload (length and content) that is checksummed and
given a sequence number. Any errors are reported and counted.
'''
import sys, os, select, random, time, optparse, logging
import logging
import optparse
import os
import random
import select
import sys
import time
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from websockify.websockifyserver import WebSockifyServer, WebSockifyRequestHandler
from websockify.websockifyserver import WebSockifyRequestHandler
from websockify.websockifyserver import WebSockifyServer
class WebSocketLoadServer(WebSockifyServer):
@ -26,7 +37,7 @@ class WebSocketLoad(WebSockifyRequestHandler):
max_packet_size = 10000
def new_websocket_client(self):
print "Prepopulating random array"
print("Prepopulating random array")
self.rand_array = []
for i in range(0, self.max_packet_size):
self.rand_array.append(random.randint(0, 9))
@ -37,19 +48,18 @@ class WebSocketLoad(WebSockifyRequestHandler):
self.responder(self.request)
print "accumulated errors:", self.errors
print("accumulated errors:", self.errors)
self.errors = 0
def responder(self, client):
c_pend = 0
cqueue = []
cpartial = ""
socks = [client]
last_send = time.time() * 1000
while True:
ins, outs, excepts = select.select(socks, socks, socks, 1)
if excepts: raise Exception("Socket exception")
if excepts:
raise Exception("Socket exception")
if client in ins:
frames, closed = self.recv_frames()
@ -57,7 +67,7 @@ class WebSocketLoad(WebSockifyRequestHandler):
err = self.check(frames)
if err:
self.errors = self.errors + 1
print err
print(err)
if closed:
break
@ -74,18 +84,13 @@ class WebSocketLoad(WebSockifyRequestHandler):
def generate(self):
length = random.randint(10, self.max_packet_size)
numlist = self.rand_array[self.max_packet_size - length:]
# Error in length
#numlist.append(5)
chksum = sum(numlist)
# Error in checksum
#numlist[0] = 5
nums = "".join([str(n) for n in numlist])
data = "^%d:%d:%d:%s$" % (self.send_cnt, length, chksum, nums)
self.send_cnt += 1
return data
def check(self, frames):
err = ""
@ -106,7 +111,7 @@ class WebSocketLoad(WebSockifyRequestHandler):
length = int(length)
chksum = int(chksum)
except ValueError:
print "\n<BOF>" + repr(data) + "<EOF>"
print("\n<BOF>" + repr(data) + "<EOF>")
err += "Invalid data format\n"
continue
@ -148,10 +153,12 @@ if __name__ == '__main__':
(opts, args) = parser.parse_args()
try:
if len(args) != 1: raise ValueError
if len(args) != 1:
raise ValueError
opts.listen_port = int(args[0])
if len(args) not in [1,2]: raise ValueError
if len(args) not in [1, 2]:
raise ValueError
opts.listen_port = int(args[0])
if len(args) == 2:
opts.delay = int(args[1])
@ -165,4 +172,3 @@ if __name__ == '__main__':
opts.web = "."
server = WebSocketLoadServer(WebSocketLoad, **opts.__dict__)
server.start_server()

View File

@ -7,8 +7,14 @@ import unittest
from unittest.mock import patch, MagicMock
from jwcrypto import jwt, jwk
try:
import redis
except ImportError:
redis = None
from websockify.token_plugins import parse_source_args, ReadOnlyTokenFile, JWTTokenApi, TokenRedis
class ParseSourceArgumentsTestCase(unittest.TestCase):
def test_parameterized(self):
params = [
@ -31,6 +37,7 @@ class ParseSourceArgumentsTestCase(unittest.TestCase):
for src, args in params:
self.assertEqual(args, parse_source_args(src))
class ReadOnlyTokenFileTestCase(unittest.TestCase):
def test_empty(self):
mock_source_file = MagicMock()
@ -232,11 +239,10 @@ class JWSTokenTestCase(unittest.TestCase):
self.assertEqual(result[0], "remote_host")
self.assertEqual(result[1], "remote_port")
class TokenRedisTestCase(unittest.TestCase):
def setUp(self):
try:
import redis
except ImportError:
if redis is None:
patcher = patch.dict(sys.modules, {'redis': MagicMock()})
patcher.start()
self.addCleanup(patcher.stop)

View File

@ -18,6 +18,7 @@
import unittest
from websockify import websocket
class FakeSocket:
def __init__(self):
self.data = b''
@ -26,6 +27,7 @@ class FakeSocket:
self.data += buf
return len(buf)
class AcceptTestCase(unittest.TestCase):
def test_success(self):
ws = websocket.WebSocket()
@ -81,7 +83,7 @@ class AcceptTestCase(unittest.TestCase):
ws.accept(sock, {'upgrade': 'websocket',
'Sec-WebSocket-Version': '13',
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q==',
'Sec-WebSocket-Protocol': 'foobar gazonk'})
'Sec-WebSocket-Protocol': 'foobar,gazonk'})
self.assertEqual(sock.data[:13], b'HTTP/1.1 101 ')
self.assertTrue(b'\r\nSec-WebSocket-Protocol: gazonk\r\n' in sock.data)
@ -101,9 +103,9 @@ class AcceptTestCase(unittest.TestCase):
sock, {'upgrade': 'websocket',
'Sec-WebSocket-Version': '13',
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q==',
'Sec-WebSocket-Protocol': 'foobar gazonk'})
'Sec-WebSocket-Protocol': 'foobar,gazonk'})
def test_protocol(self):
def test_unsupported_protocol(self):
class ProtoSocket(websocket.WebSocket):
def select_subprotocol(self, protocol):
return 'oddball'
@ -114,7 +116,8 @@ class AcceptTestCase(unittest.TestCase):
sock, {'upgrade': 'websocket',
'Sec-WebSocket-Version': '13',
'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q==',
'Sec-WebSocket-Protocol': 'foobar gazonk'})
'Sec-WebSocket-Protocol': 'foobar,gazonk'})
class PingPongTest(unittest.TestCase):
def setUp(self):
@ -142,6 +145,7 @@ class PingPongTest(unittest.TestCase):
self.ws.pong(b'foo')
self.assertEqual(self.sock.data, b'\x8a\x03foo')
class HyBiEncodeDecodeTestCase(unittest.TestCase):
def test_decode_hybi_text(self):
buf = b'\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58'

View File

@ -16,8 +16,6 @@
""" Unit tests for websocketproxy """
import sys
import unittest
import unittest
import socket
from io import StringIO
@ -58,6 +56,7 @@ class FakeServer:
self.ssl_target = None
self.unix_target = None
class ProxyRequestHandlerTestCase(unittest.TestCase):
def setUp(self):
super().setUp()
@ -128,4 +127,3 @@ class ProxyRequestHandlerTestCase(unittest.TestCase):
self.handler.server.target_host = "someotherhost"
self.handler.auth_connection()

View File

@ -66,4 +66,3 @@ class HttpWebSocketTest(unittest.TestCase):
# Then
req_obj.end_headers.assert_called_once_with()

View File

@ -17,18 +17,12 @@
""" Unit tests for websockifyserver """
import errno
import os
import logging
import select
import shutil
import socket
import ssl
from unittest.mock import patch, MagicMock, ANY
from unittest.mock import patch
import sys
import tempfile
import unittest
import socket
import signal
from http.server import BaseHTTPRequestHandler
from io import StringIO
from io import BytesIO
@ -237,6 +231,7 @@ class WebSockifyServerTestCase(unittest.TestCase):
def test_do_handshake_no_ssl(self):
class FakeHandler:
CALLED = False
def __init__(self, *args, **kwargs):
type(self).CALLED = True
@ -292,12 +287,16 @@ class WebSockifyServerTestCase(unittest.TestCase):
def __init__(self, purpose):
self.verify_mode = None
self.options = 0
def load_cert_chain(self, certfile, keyfile, password):
pass
def set_default_verify_paths(self):
pass
def load_verify_locations(self, cafile):
pass
def wrap_socket(self, *args, **kwargs):
raise ssl.SSLError(ssl.SSL_ERROR_EOF)
@ -323,17 +322,23 @@ class WebSockifyServerTestCase(unittest.TestCase):
class fake_create_default_context():
CIPHERS = ''
def __init__(self, purpose):
self.verify_mode = None
self.options = 0
def load_cert_chain(self, certfile, keyfile, password):
pass
def set_default_verify_paths(self):
pass
def load_verify_locations(self, cafile):
pass
def wrap_socket(self, *args, **kwargs):
pass
def set_ciphers(self, ciphers_to_set):
fake_create_default_context.CIPHERS = ciphers_to_set
@ -358,19 +363,26 @@ class WebSockifyServerTestCase(unittest.TestCase):
class fake_create_default_context:
OPTIONS = 0
def __init__(self, purpose):
self.verify_mode = None
self._options = 0
def load_cert_chain(self, certfile, keyfile, password):
pass
def set_default_verify_paths(self):
pass
def load_verify_locations(self, cafile):
pass
def wrap_socket(self, *args, **kwargs):
pass
def get_options(self):
return self._options
def set_options(self, val):
fake_create_default_context.OPTIONS = val
options = property(get_options, set_options)
@ -386,7 +398,6 @@ class WebSockifyServerTestCase(unittest.TestCase):
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")
@ -398,7 +409,6 @@ class WebSockifyServerTestCase(unittest.TestCase):
def test_start_server_keyboardinterrupt(self):
server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1)
sock = server.socket('localhost')
def fake_select(rlist, wlist, xlist, timeout=None):
raise KeyboardInterrupt
@ -410,7 +420,6 @@ class WebSockifyServerTestCase(unittest.TestCase):
def test_start_server_systemexit(self):
server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1)
sock = server.socket('localhost')
def fake_select(rlist, wlist, xlist, timeout=None):
sys.exit()

13
tox.ini
View File

@ -12,6 +12,13 @@ deps = -r{toxinidir}/test-requirements.txt
# At some point we should enable this since tox expects it to exist but
# the code will need pep8ising first.
#[testenv:pep8]
#commands = flake8
#dep = flake8
[testenv:pep8]
commands = flake8
deps = flake8
[flake8]
max-line-length = 160
# E129 visually indented line with same indent as next logical line
# W503 line break before binary operator
# W504 line break after binary operator
ignore = E129,W503,W504

View File

@ -1,2 +1,2 @@
from websockify.websocket import *
from websockify.websocketproxy import *
from websockify.websocket import * # noqa: F401,F403
from websockify.websocketproxy import * # noqa: F401,F403

View File

@ -76,6 +76,7 @@ class BasicHTTPAuth():
raise AuthenticationError(response_code=401,
response_headers={'WWW-Authenticate': 'Basic realm="Websockify"'})
class ExpectOrigin():
def __init__(self, src=None):
if src is None:
@ -88,6 +89,7 @@ class ExpectOrigin():
if origin is None or origin not in self.source:
raise InvalidOriginError(expected=self.source, actual=origin)
class ClientCertCNAuth():
"""Verifies client by SSL certificate. Specify src as whitespace separated list of common names."""

View File

@ -1,4 +1,7 @@
import logging.handlers as handlers, socket, os, time
import logging.handlers as handlers
import os
import socket
import time
class WebsockifySysLogHandler(handlers.SysLogHandler):
@ -16,11 +19,8 @@ class WebsockifySysLogHandler(handlers.SysLogHandler):
_max_ident = 24 # safer for old daemons
_send_length = False
_tail = '\n'
ident = None
def __init__(self, address=('localhost', handlers.SYSLOG_UDP_PORT),
facility=handlers.SysLogHandler.LOG_USER,
socktype=None, ident=None, legacy=False):
@ -46,7 +46,6 @@ class WebsockifySysLogHandler(handlers.SysLogHandler):
super().__init__(address, facility, socktype)
def emit(self, record):
"""
Emit a record.
@ -64,7 +63,7 @@ class WebsockifySysLogHandler(handlers.SysLogHandler):
pri = self.encodePriority(self.facility,
self.mapPriority(record.levelname))
timestamp = time.strftime(self._timestamp_fmt, time.gmtime());
timestamp = time.strftime(self._timestamp_fmt, time.gmtime())
hostname = socket.gethostname()[:self._max_hostname]
@ -112,7 +111,5 @@ class WebsockifySysLogHandler(handlers.SysLogHandler):
else:
self.socket.sendall(msg)
except (KeyboardInterrupt, SystemExit):
raise
except:
except Exception:
self.handleError(record)

View File

@ -5,6 +5,11 @@ import re
import json
from pathlib import Path
try:
import redis
except ImportError:
redis = None
logger = logging.getLogger(__name__)
_SOURCE_SPLIT_REGEX = re.compile(
@ -84,6 +89,7 @@ class TokenFile(ReadOnlyTokenFile):
return super().lookup(token)
class TokenFileName(BasePlugin):
# source is a directory
# token is filename
@ -156,10 +162,10 @@ class JWTTokenApi(BasePlugin):
try:
key.import_from_pem(key_data)
except:
except Exception:
try:
key.import_key(k=key_data.decode('utf-8'), kty='oct')
except:
except Exception:
logger.error('Failed to correctly parse key data!')
return None
@ -255,9 +261,7 @@ class TokenRedis(BasePlugin):
pip install redis
"""
def __init__(self, src):
try:
import redis
except ImportError:
if redis is None:
logger.error("Unable to load redis module")
sys.exit()
# Default values
@ -313,9 +317,7 @@ class TokenRedis(BasePlugin):
sys.exit()
def lookup(self, token):
try:
import redis
except ImportError:
if redis is None:
logger.error("package redis not found, are you sure you've installed them correctly?")
sys.exit()

View File

@ -31,11 +31,15 @@ except ImportError:
warnings.warn("no 'numpy' module, HyBi protocol will be slower")
numpy = None
class WebSocketWantReadError(ssl.SSLWantReadError):
pass
class WebSocketWantWriteError(ssl.SSLWantWriteError):
pass
class WebSocket:
"""WebSocket protocol socket like class.
@ -118,7 +122,7 @@ class WebSocket:
connect() must retain the same arguments.
"""
self.client = True;
self.client = True
uri = urlparse(uri)
@ -206,7 +210,7 @@ class WebSocket:
accept = headers.get('Sec-WebSocket-Accept')
if accept is None:
raise Exception("Missing Sec-WebSocket-Accept header");
raise Exception("Missing Sec-WebSocket-Accept header")
expected = sha1((self._key + self.GUID).encode("ascii")).digest()
expected = b64encode(expected).decode("ascii")
@ -214,7 +218,7 @@ class WebSocket:
del self._key
if accept != expected:
raise Exception("Invalid Sec-WebSocket-Accept header");
raise Exception("Invalid Sec-WebSocket-Accept header")
self.protocol = headers.get('Sec-WebSocket-Protocol')
if len(protocols) == 0:
@ -258,7 +262,7 @@ class WebSocket:
ver = headers.get('Sec-WebSocket-Version')
if ver is None:
raise Exception("Missing Sec-WebSocket-Version header");
raise Exception("Missing Sec-WebSocket-Version header")
# HyBi-07 report version 7
# HyBi-08 - HyBi-12 report version 8
@ -270,7 +274,7 @@ class WebSocket:
key = headers.get('Sec-WebSocket-Key')
if key is None:
raise Exception("Missing Sec-WebSocket-Key header");
raise Exception("Missing Sec-WebSocket-Key header")
# Generate the hash value for the accept header
accept = sha1((key + self.GUID).encode("ascii")).digest()
@ -753,8 +757,6 @@ class WebSocket:
# Unmask a frame
if numpy:
plen = len(buf)
pstart = 0
pend = plen
b = c = b''
if plen >= 4:
dtype = numpy.dtype('<u4')
@ -762,7 +764,6 @@ class WebSocket:
dtype = dtype.newbyteorder('>')
mask = numpy.frombuffer(mask, dtype, count=1)
data = numpy.frombuffer(buf, dtype, count=int(plen / 4))
#b = numpy.bitwise_xor(data, mask).data
b = numpy.bitwise_xor(data, mask).tobytes()
if plen % 4:
@ -873,4 +874,3 @@ class WebSocket:
f['payload'] = buf[hlen:(hlen + length)]
return f

View File

@ -11,14 +11,26 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
import signal, socket, optparse, time, os, sys, subprocess, logging, errno, ssl, stat
from socketserver import ThreadingMixIn
import errno
from http.server import HTTPServer
import logging
import optparse
import os
import select
import signal
import socket
from socketserver import ThreadingMixIn
import ssl
import stat
import subprocess
import sys
import time
from urllib.parse import parse_qs
from urllib.parse import urlparse
from websockify import websockifyserver
from websockify import auth_plugins as auth
from urllib.parse import parse_qs, urlparse
class ProxyRequestHandler(websockifyserver.WebSockifyRequestHandler):
@ -200,8 +212,10 @@ Traffic Legend:
self.heartbeat = now + self.server.heartbeat
self.send_ping()
if tqueue: wlist.append(target)
if cqueue or c_pend: wlist.append(self.request)
if tqueue:
wlist.append(target)
if cqueue or c_pend:
wlist.append(self.request)
try:
ins, outs, excepts = select.select(rlist, wlist, [], 1)
except OSError:
@ -216,7 +230,8 @@ Traffic Legend:
else:
continue
if excepts: raise Exception("Socket exception")
if excepts:
raise Exception("Socket exception")
if self.request in outs:
# Send queued target data to the client
@ -248,7 +263,6 @@ Traffic Legend:
self.server.target_host, self.server.target_port)
raise self.CClose(closed['code'], closed['reason'])
if target in outs:
# Send queued client data to the target
dat = tqueue.pop(0)
@ -260,7 +274,6 @@ Traffic Legend:
tqueue.insert(0, dat[sent:])
self.print_traffic(".>")
if target in ins:
# Receive target data, encode it and queue for client
buf = target.recv(self.buffer_size)
@ -283,6 +296,7 @@ Traffic Legend:
cqueue.append(buf)
self.print_traffic("{")
class WebSocketProxy(websockifyserver.WebSockifyServer):
"""
Proxy traffic to and from a WebSockets client to a normal TCP
@ -364,7 +378,7 @@ class WebSocketProxy(websockifyserver.WebSockifyServer):
else:
dst_string = "%s:%s" % (self.target_host, self.target_port)
if self.listen_fd != None:
if self.listen_fd is not None:
src_string = "inetd"
else:
src_string = "%s:%s" % (self.listen_host, self.listen_port)
@ -389,11 +403,11 @@ class WebSocketProxy(websockifyserver.WebSockifyServer):
if self.wrap_cmd and self.cmd:
ret = self.cmd.poll()
if ret != None:
if ret is not None:
self.vmsg("Wrapped command exited (or daemon). Returned %s" % ret)
self.cmd = None
if self.wrap_cmd and self.cmd == None:
if self.wrap_cmd and self.cmd is None:
# Response to wrapped command being gone
if self.wrap_mode == "ignore":
pass
@ -427,6 +441,7 @@ SSL_OPTIONS = {
ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2,
}
def select_ssl_version(version):
"""Returns SSL options for the most secure TSL version available on this
Python version"""
@ -444,6 +459,7 @@ def select_ssl_version(version):
return SSL_OPTIONS[fallback]
def websockify_init():
# Setup basic logging to stderr.
stderr_handler = logging.StreamHandler()
@ -562,9 +578,7 @@ def websockify_init():
(opts, args) = parser.parse_args()
# Validate options.
if opts.token_source and not opts.token_plugin:
parser.error("You must use --token-plugin to use --token-source")
@ -583,11 +597,9 @@ def websockify_init():
if opts.legacy_syslog and not opts.syslog:
parser.error("You must use --syslog to use --legacy-syslog")
opts.ssl_options = select_ssl_version(opts.ssl_version)
del opts.ssl_version
if opts.log_file:
# Setup logging to user-specified file.
opts.log_file = os.path.abspath(opts.log_file)
@ -638,7 +650,6 @@ def websockify_init():
root = logging.getLogger()
root.setLevel(logging.DEBUG)
# Transform to absolute path as daemon may chdir
if opts.target_cfg:
opts.target_cfg = os.path.abspath(opts.target_cfg)
@ -655,7 +666,7 @@ def websockify_init():
opts.wrap_cmd = None
if not websockifyserver.ssl and opts.ssl_target:
parser.error("SSL target requested and Python SSL module not loaded.");
parser.error("SSL target requested and Python SSL module not loaded.")
if opts.ssl_only and not os.path.exists(opts.cert):
parser.error("SSL only and %s not found" % opts.cert)
@ -708,7 +719,7 @@ def websockify_init():
except ValueError:
parser.error("Error parsing target port")
if len(args) > 0 and opts.wrap_cmd == None:
if len(args) > 0 and opts.wrap_cmd is None:
parser.error("Too many arguments")
if opts.token_plugin is not None:
@ -795,7 +806,6 @@ class LibProxyServer(ThreadingMixIn, HTTPServer):
super().__init__((listen_host, listen_port), RequestHandlerClass)
def process_request(self, request, client_address):
"""Override process_request to implement a counter"""
self.handler_id += 1

View File

@ -10,7 +10,8 @@ Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
import sys
from http.server import BaseHTTPRequestHandler, HTTPServer
from websockify.websocket import WebSocket, WebSocketWantReadError, WebSocketWantWriteError
from websockify.websocket import WebSocket
class HttpWebSocket(WebSocket):
"""Class to glue websocket and http request functionality together"""
@ -100,11 +101,14 @@ class WebSocketRequestHandlerMixIn:
"""
pass
# Convenient ready made classes
class WebSocketRequestHandler(WebSocketRequestHandlerMixIn,
BaseHTTPRequestHandler):
pass
class WebSocketServer(HTTPServer):
pass

View File

@ -12,18 +12,29 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
import os, sys, time, errno, signal, socket, select, logging
import multiprocessing
import errno
from http.server import SimpleHTTPRequestHandler
import logging
import multiprocessing
import os
import select
import signal
import socket
import sys
import time
# Degraded functionality if these imports are missing
for mod, msg in [('ssl', 'TLS/SSL/wss is disabled'),
('resource', 'daemonizing is disabled')]:
try:
globals()[mod] = __import__(mod)
import ssl
except ImportError:
globals()[mod] = None
print("WARNING: no '%s' module, %s" % (mod, msg))
ssl = None
print("WARNING: no 'ssl' module, TLS/SSL/wss is disabled")
try:
import resource
except ImportError:
resource = None
print("WARNING: no 'resource' module, daemonizing is disabled")
if sys.platform == 'win32':
# make sockets pickle-able/inheritable
@ -32,6 +43,7 @@ if sys.platform == 'win32':
from websockify.websocket import WebSocketWantReadError, WebSocketWantWriteError
from websockify.websocketserver import WebSocketRequestHandlerMixIn
class CompatibleWebSocket(WebSocketRequestHandlerMixIn.SocketClass):
def select_subprotocol(self, protocols):
# Handle old websockify clients that still specify a sub-protocol
@ -40,6 +52,7 @@ class CompatibleWebSocket(WebSocketRequestHandlerMixIn.SocketClass):
else:
return ''
# HTTP handler with WebSocket upgrade support
class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHandler):
"""
@ -403,9 +416,9 @@ class WebSockifyServer():
# Show configuration
self.msg("WebSocket server settings:")
if self.listen_fd != None:
if self.listen_fd is not None:
self.msg(" - Listen for inetd connections")
elif self.unix_listen != None:
elif self.unix_listen is not None:
self.msg(" - Listen on unix socket %s", self.unix_listen)
else:
self.msg(" - Listen on %s:%s",
@ -454,7 +467,7 @@ class WebSockifyServer():
if connect and not (port or unix_socket):
raise Exception("Connect mode requires a port")
if use_ssl and not ssl:
raise Exception("SSL socket requested but Python SSL module not loaded.");
raise Exception("SSL socket requested but Python SSL module not loaded.")
if not connect and use_ssl:
raise Exception("SSL only supported in connect mode (for now)")
if not connect:
@ -526,9 +539,11 @@ class WebSockifyServer():
os.setuid(os.getuid()) # relinquish elevations
# Double fork to daemonize
if os.fork() > 0: os._exit(0) # Parent exits
if os.fork() > 0: # Parent exits
os._exit(0)
os.setsid() # Obtain new process group
if os.fork() > 0: os._exit(0) # Parent exits
if os.fork() > 0: # Parent exits
os._exit(0)
# Signal handling
signal.signal(signal.SIGTERM, signal.SIG_IGN)
@ -536,14 +551,16 @@ class WebSockifyServer():
# Close open files
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
if maxfd == resource.RLIM_INFINITY: maxfd = 256
if maxfd == resource.RLIM_INFINITY:
maxfd = 256
for fd in reversed(range(maxfd)):
try:
if fd not in keepfd:
os.close(fd)
except OSError:
_, exc, _ = sys.exc_info()
if exc.errno != errno.EBADF: raise
if exc.errno != errno.EBADF:
raise
# Redirect I/O to /dev/null
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno())
@ -572,7 +589,6 @@ class WebSockifyServer():
# Peek, but do not read the data so that we have a opportunity
# to SSL wrap the socket first
handshake = sock.recv(1024, socket.MSG_PEEK)
#self.msg("Handshake [%s]" % handshake)
if not handshake:
raise self.EClose("")
@ -644,17 +660,16 @@ class WebSockifyServer():
""" Same as msg() but as warning. """
self.logger.log(logging.WARNING, *args, **kwargs)
#
# Events that can/should be overridden in sub-classes
#
def started(self):
""" Called after WebSockets startup """
self.vmsg("WebSockets server started")
def poll(self):
""" Run periodically while waiting for connections. """
#self.vmsg("Running poll()")
pass
def terminate(self):
@ -735,9 +750,9 @@ class WebSockifyServer():
"""
try:
if self.listen_fd != None:
if self.listen_fd is not None:
lsock = socket.fromfd(self.listen_fd, socket.AF_INET, socket.SOCK_STREAM)
elif self.unix_listen != None:
elif self.unix_listen is not None:
lsock = self.socket(host=None,
unix_socket=self.unix_listen,
unix_socket_mode=self.unix_listen_mode,
@ -781,7 +796,7 @@ class WebSockifyServer():
try:
try:
startsock = None
pid = err = 0
err = 0
child_count = 0
# Collect zombie child processes
@ -813,7 +828,7 @@ class WebSockifyServer():
if lsock in ready:
startsock, address = lsock.accept()
# Unix Socket will not report address (empty string), but address[0] is logged a bunch
if self.unix_listen != None:
if self.unix_listen is not None:
address = [self.unix_listen]
else:
continue
@ -879,5 +894,3 @@ class WebSockifyServer():
# Restore signals
for sig, func in original_signals.items():
signal.signal(sig, func)