Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-websockify for
openSUSE:Factory checked in at 2025-11-11 19:18:42
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-websockify (Old)
and /work/SRC/openSUSE:Factory/.python-websockify.new.1980 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-websockify"
Tue Nov 11 19:18:42 2025 rev:30 rq:1316814 version:0.13.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-websockify/python-websockify.changes
2025-08-27 21:34:39.870645583 +0200
+++
/work/SRC/openSUSE:Factory/.python-websockify.new.1980/python-websockify.changes
2025-11-11 19:19:02.037682275 +0100
@@ -1,0 +2,11 @@
+Mon Nov 10 08:20:41 UTC 2025 - Dirk Müller <[email protected]>
+
+- update to 0.13.0:
+ * Support for Python < 3.6 has been dropped.
+ * SNI is enabled when connecting to an SSL target as an SSL
+ client.
+ * The TokenRedis plugin handles namespaces.
+ * Headers are sanitized before being passed to authentication
+ plugins.
+
+-------------------------------------------------------------------
Old:
----
v0.12.0.tar.gz
New:
----
v0.13.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-websockify.spec ++++++
--- /var/tmp/diff_new_pack.bZLx8u/_old 2025-11-11 19:19:02.677709180 +0100
+++ /var/tmp/diff_new_pack.bZLx8u/_new 2025-11-11 19:19:02.681709347 +0100
@@ -23,7 +23,7 @@
%endif
%{?sle15_python_module_pythons}
Name: python-websockify
-Version: 0.12.0
+Version: 0.13.0
Release: 0
Summary: WebSocket to TCP proxy/bridge
License: BSD-2-Clause AND LGPL-3.0-only AND MPL-2.0 AND BSD-3-Clause
++++++ v0.12.0.tar.gz -> v0.13.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/websockify-0.12.0/CHANGES.txt
new/websockify-0.13.0/CHANGES.txt
--- old/websockify-0.12.0/CHANGES.txt 2024-06-03 14:40:01.000000000 +0200
+++ new/websockify-0.13.0/CHANGES.txt 2025-02-12 11:45:01.000000000 +0100
@@ -1,6 +1,14 @@
Changes
=======
+0.13.0
+------
+
+* Support for Python < 3.6 has been dropped.
+* SNI is enabled when connecting to an SSL target as an SSL client.
+* The TokenRedis plugin handles namespaces.
+* Headers are sanitized before being passed to authentication plugins.
+
0.12.0
------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/websockify-0.12.0/setup.py
new/websockify-0.13.0/setup.py
--- old/websockify-0.12.0/setup.py 2024-06-03 14:40:01.000000000 +0200
+++ new/websockify-0.13.0/setup.py 2025-02-12 11:45:01.000000000 +0100
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages
-version = '0.12.0'
+version = '0.13.0'
name = 'websockify'
long_description = open("README.md").read() + "\n" + \
open("CHANGES.txt").read() + "\n"
@@ -14,12 +14,13 @@
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
- "Programming Language :: Python :: 3.4",
- "Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
],
keywords='noVNC websockify',
license='LGPLv3',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/websockify-0.12.0/tests/test_token_plugins.py
new/websockify-0.13.0/tests/test_token_plugins.py
--- old/websockify-0.12.0/tests/test_token_plugins.py 2024-06-03
14:40:01.000000000 +0200
+++ new/websockify-0.13.0/tests/test_token_plugins.py 2025-02-12
11:45:01.000000000 +0100
@@ -7,7 +7,29 @@
from unittest.mock import patch, mock_open, MagicMock
from jwcrypto import jwt, jwk
-from websockify.token_plugins import ReadOnlyTokenFile, JWTTokenApi, TokenRedis
+from websockify.token_plugins import parse_source_args, ReadOnlyTokenFile,
JWTTokenApi, TokenRedis
+
+class ParseSourceArgumentsTestCase(unittest.TestCase):
+ def test_parameterized(self):
+ params = [
+ ('', ['']),
+ (':', ['', '']),
+ ('::', ['', '', '']),
+ ('"', ['"']),
+ ('""', ['""']),
+ ('"""', ['"""']),
+ ('"localhost"', ['localhost']),
+ ('"localhost":', ['localhost', '']),
+ ('"localhost"::', ['localhost', '', '']),
+ ('"local:host"', ['local:host']),
+ ('"local:host:"pass"', ['"local', 'host', "pass"]),
+ ('"local":"host"', ['local', 'host']),
+ ('"local":host"', ['local', 'host"']),
+ ('localhost:6379:1:pass"word:"my-app-namespace:dev"',
+ ['localhost', '6379', '1', 'pass"word', 'my-app-namespace:dev']),
+ ]
+ for src, args in params:
+ self.assertEqual(args, parse_source_args(src))
class ReadOnlyTokenFileTestCase(unittest.TestCase):
patch('os.path.isdir', MagicMock(return_value=False))
@@ -267,6 +289,42 @@
instance.get.assert_called_once_with('testhost')
self.assertIsNone(result)
+ @patch('redis.Redis')
+ def test_token_without_namespace(self, mock_redis):
+ plugin = TokenRedis('127.0.0.1:1234')
+ token = 'testhost'
+
+ def mock_redis_get(key):
+ self.assertEqual(key, token)
+ return b'remote_host:remote_port'
+
+ instance = mock_redis.return_value
+ instance.get = mock_redis_get
+
+ result = plugin.lookup(token)
+
+ self.assertIsNotNone(result)
+ self.assertEqual(result[0], 'remote_host')
+ self.assertEqual(result[1], 'remote_port')
+
+ @patch('redis.Redis')
+ def test_token_with_namespace(self, mock_redis):
+ plugin = TokenRedis('127.0.0.1:1234:::namespace')
+ token = 'testhost'
+
+ def mock_redis_get(key):
+ self.assertEqual(key, "namespace:" + token)
+ return b'remote_host:remote_port'
+
+ instance = mock_redis.return_value
+ instance.get = mock_redis_get
+
+ result = plugin.lookup(token)
+
+ self.assertIsNotNone(result)
+ self.assertEqual(result[0], 'remote_host')
+ self.assertEqual(result[1], 'remote_port')
+
def test_src_only_host(self):
plugin = TokenRedis('127.0.0.1')
@@ -274,6 +332,7 @@
self.assertEqual(plugin._port, 6379)
self.assertEqual(plugin._db, 0)
self.assertEqual(plugin._password, None)
+ self.assertEqual(plugin._namespace, "")
def test_src_with_host_port(self):
plugin = TokenRedis('127.0.0.1:1234')
@@ -282,6 +341,7 @@
self.assertEqual(plugin._port, 1234)
self.assertEqual(plugin._db, 0)
self.assertEqual(plugin._password, None)
+ self.assertEqual(plugin._namespace, "")
def test_src_with_host_port_db(self):
plugin = TokenRedis('127.0.0.1:1234:2')
@@ -290,6 +350,7 @@
self.assertEqual(plugin._port, 1234)
self.assertEqual(plugin._db, 2)
self.assertEqual(plugin._password, None)
+ self.assertEqual(plugin._namespace, "")
def test_src_with_host_port_db_pass(self):
plugin = TokenRedis('127.0.0.1:1234:2:verysecret')
@@ -298,67 +359,112 @@
self.assertEqual(plugin._port, 1234)
self.assertEqual(plugin._db, 2)
self.assertEqual(plugin._password, 'verysecret')
+ self.assertEqual(plugin._namespace, "")
- def test_src_with_host_empty_port_empty_db_pass(self):
+ def test_src_with_host_port_db_pass_namespace(self):
+ plugin = TokenRedis('127.0.0.1:1234:2:verysecret:namespace')
+
+ self.assertEqual(plugin._server, '127.0.0.1')
+ self.assertEqual(plugin._port, 1234)
+ self.assertEqual(plugin._db, 2)
+ self.assertEqual(plugin._password, 'verysecret')
+ self.assertEqual(plugin._namespace, "namespace:")
+
+ def test_src_with_host_empty_port_empty_db_pass_no_namespace(self):
plugin = TokenRedis('127.0.0.1:::verysecret')
self.assertEqual(plugin._server, '127.0.0.1')
self.assertEqual(plugin._port, 6379)
self.assertEqual(plugin._db, 0)
self.assertEqual(plugin._password, 'verysecret')
+ self.assertEqual(plugin._namespace, "")
+
+ def
test_src_with_host_empty_port_empty_db_empty_pass_empty_namespace(self):
+ plugin = TokenRedis('127.0.0.1::::')
- def test_src_with_host_empty_port_empty_db_empty_pass(self):
+ self.assertEqual(plugin._server, '127.0.0.1')
+ self.assertEqual(plugin._port, 6379)
+ self.assertEqual(plugin._db, 0)
+ self.assertEqual(plugin._password, None)
+ self.assertEqual(plugin._namespace, "")
+
+ def test_src_with_host_empty_port_empty_db_empty_pass_no_namespace(self):
plugin = TokenRedis('127.0.0.1:::')
self.assertEqual(plugin._server, '127.0.0.1')
self.assertEqual(plugin._port, 6379)
self.assertEqual(plugin._db, 0)
self.assertEqual(plugin._password, None)
+ self.assertEqual(plugin._namespace, "")
- def test_src_with_host_empty_port_empty_db_no_pass(self):
+ def test_src_with_host_empty_port_empty_db_no_pass_no_namespace(self):
plugin = TokenRedis('127.0.0.1::')
self.assertEqual(plugin._server, '127.0.0.1')
self.assertEqual(plugin._port, 6379)
self.assertEqual(plugin._db, 0)
self.assertEqual(plugin._password, None)
+ self.assertEqual(plugin._namespace, "")
- def test_src_with_host_empty_port_no_db_no_pass(self):
+ def test_src_with_host_empty_port_no_db_no_pass_no_namespace(self):
plugin = TokenRedis('127.0.0.1:')
self.assertEqual(plugin._server, '127.0.0.1')
self.assertEqual(plugin._port, 6379)
self.assertEqual(plugin._db, 0)
self.assertEqual(plugin._password, None)
+ self.assertEqual(plugin._namespace, "")
+
+ def test_src_with_host_empty_port_empty_db_empty_pass_namespace(self):
+ plugin = TokenRedis('127.0.0.1::::namespace')
+
+ self.assertEqual(plugin._server, '127.0.0.1')
+ self.assertEqual(plugin._port, 6379)
+ self.assertEqual(plugin._db, 0)
+ self.assertEqual(plugin._password, None)
+ self.assertEqual(plugin._namespace, "namespace:")
+
+ def
test_src_with_host_empty_port_empty_db_empty_pass_nested_namespace(self):
+ plugin = TokenRedis('127.0.0.1::::"ns1:ns2"')
+
+ self.assertEqual(plugin._server, '127.0.0.1')
+ self.assertEqual(plugin._port, 6379)
+ self.assertEqual(plugin._db, 0)
+ self.assertEqual(plugin._password, None)
+ self.assertEqual(plugin._namespace, "ns1:ns2:")
- def test_src_with_host_empty_port_db_no_pass(self):
+ def test_src_with_host_empty_port_db_no_pass_no_namespace(self):
plugin = TokenRedis('127.0.0.1::2')
self.assertEqual(plugin._server, '127.0.0.1')
self.assertEqual(plugin._port, 6379)
self.assertEqual(plugin._db, 2)
self.assertEqual(plugin._password, None)
+ self.assertEqual(plugin._namespace, "")
- def test_src_with_host_port_empty_db_pass(self):
+ def test_src_with_host_port_empty_db_pass_no_namespace(self):
plugin = TokenRedis('127.0.0.1:1234::verysecret')
self.assertEqual(plugin._server, '127.0.0.1')
self.assertEqual(plugin._port, 1234)
self.assertEqual(plugin._db, 0)
self.assertEqual(plugin._password, 'verysecret')
+ self.assertEqual(plugin._namespace, "")
- def test_src_with_host_empty_port_db_pass(self):
+ def test_src_with_host_empty_port_db_pass_no_namespace(self):
plugin = TokenRedis('127.0.0.1::2:verysecret')
self.assertEqual(plugin._server, '127.0.0.1')
self.assertEqual(plugin._port, 6379)
self.assertEqual(plugin._db, 2)
self.assertEqual(plugin._password, 'verysecret')
+ self.assertEqual(plugin._namespace, "")
- def test_src_with_host_empty_port_db_empty_pass(self):
+ def test_src_with_host_empty_port_db_empty_pass_no_namespace(self):
plugin = TokenRedis('127.0.0.1::2:')
self.assertEqual(plugin._server, '127.0.0.1')
self.assertEqual(plugin._port, 6379)
self.assertEqual(plugin._db, 2)
self.assertEqual(plugin._password, None)
+ self.assertEqual(plugin._namespace, "")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/websockify-0.12.0/tests/test_websocketproxy.py
new/websockify-0.13.0/tests/test_websocketproxy.py
--- old/websockify-0.12.0/tests/test_websocketproxy.py 2024-06-03
14:40:01.000000000 +0200
+++ new/websockify-0.13.0/tests/test_websocketproxy.py 2025-02-12
11:45:01.000000000 +0100
@@ -29,7 +29,7 @@
from websockify import auth_plugins
-class FakeSocket(object):
+class FakeSocket:
def __init__(self, data=b''):
self._data = data
@@ -47,7 +47,7 @@
return StringIO(self._data.decode('latin_1'))
-class FakeServer(object):
+class FakeServer:
class EClose(Exception):
pass
@@ -60,16 +60,16 @@
class ProxyRequestHandlerTestCase(unittest.TestCase):
def setUp(self):
- super(ProxyRequestHandlerTestCase, self).setUp()
+ super().setUp()
self.handler = websocketproxy.ProxyRequestHandler(
FakeSocket(), "127.0.0.1", FakeServer())
self.handler.path = "https://localhost:6080/websockify?token=blah"
- self.handler.headers = None
+ self.handler.headers = {}
patch('websockify.websockifyserver.WebSockifyServer.socket').start()
def tearDown(self):
patch.stopall()
- super(ProxyRequestHandlerTestCase, self).tearDown()
+ super().tearDown()
def test_get_target(self):
class TestPlugin(token_plugins.BasePlugin):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/websockify-0.12.0/tests/test_websockifyserver.py
new/websockify-0.13.0/tests/test_websockifyserver.py
--- old/websockify-0.12.0/tests/test_websockifyserver.py 2024-06-03
14:40:01.000000000 +0200
+++ new/websockify-0.13.0/tests/test_websockifyserver.py 2025-02-12
11:45:01.000000000 +0100
@@ -39,7 +39,7 @@
raise OSError('fake error')
-class FakeSocket(object):
+class FakeSocket:
def __init__(self, data=b''):
self._data = data
@@ -59,7 +59,7 @@
class WebSockifyRequestHandlerTestCase(unittest.TestCase):
def setUp(self):
- super(WebSockifyRequestHandlerTestCase, self).setUp()
+ super().setUp()
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
# Mock this out cause it screws tests up
patch('os.chdir').start()
@@ -68,7 +68,7 @@
"""Called automatically after each test."""
patch.stopall()
os.rmdir(self.tmpdir)
- super(WebSockifyRequestHandlerTestCase, self).tearDown()
+ super().tearDown()
def _get_server(self,
handler_class=websockifyserver.WebSockifyRequestHandler,
**kwargs):
@@ -101,7 +101,7 @@
class WebSockifyServerTestCase(unittest.TestCase):
def setUp(self):
- super(WebSockifyServerTestCase, self).setUp()
+ super().setUp()
self.tmpdir = tempfile.mkdtemp('-websockify-tests')
# Mock this out cause it screws tests up
patch('os.chdir').start()
@@ -110,7 +110,7 @@
"""Called automatically after each test."""
patch.stopall()
os.rmdir(self.tmpdir)
- super(WebSockifyServerTestCase, self).tearDown()
+ super().tearDown()
def _get_server(self,
handler_class=websockifyserver.WebSockifyRequestHandler,
**kwargs):
@@ -181,7 +181,7 @@
sock, '127.0.0.1')
def test_do_handshake_no_ssl(self):
- class FakeHandler(object):
+ class FakeHandler:
CALLED = False
def __init__(self, *args, **kwargs):
type(self).CALLED = True
@@ -256,7 +256,7 @@
def test_do_handshake_ssl_sets_ciphers(self):
test_ciphers = 'TEST-CIPHERS-1:TEST-CIPHER-2'
- class FakeHandler(object):
+ class FakeHandler:
def __init__(self, *args, **kwargs):
pass
@@ -291,7 +291,7 @@
def test_do_handshake_ssl_sets_opions(self):
test_options = 0xCAFEBEEF
- class FakeHandler(object):
+ class FakeHandler:
def __init__(self, *args, **kwargs):
pass
@@ -302,7 +302,7 @@
def fake_select(rlist, wlist, xlist, timeout=None):
return ([sock], [], [])
- class fake_create_default_context(object):
+ class fake_create_default_context:
OPTIONS = 0
def __init__(self, purpose):
self.verify_mode = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/websockify-0.12.0/websockify/sysloghandler.py
new/websockify-0.13.0/websockify/sysloghandler.py
--- old/websockify-0.12.0/websockify/sysloghandler.py 2024-06-03
14:40:01.000000000 +0200
+++ new/websockify-0.13.0/websockify/sysloghandler.py 2025-02-12
11:45:01.000000000 +0100
@@ -1,118 +1,118 @@
-import logging.handlers as handlers, socket, os, time
-
-
-class WebsockifySysLogHandler(handlers.SysLogHandler):
- """
- A handler class that sends proper Syslog-formatted messages,
- as defined by RFC 5424.
- """
-
- _legacy_head_fmt = '<{pri}>{ident}[{pid}]: '
- _rfc5424_head_fmt = '<{pri}>1 {timestamp} {hostname} {ident} {pid} - - '
- _head_fmt = _rfc5424_head_fmt
- _legacy = False
- _timestamp_fmt = '%Y-%m-%dT%H:%M:%SZ'
- _max_hostname = 255
- _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):
- """
- Initialize a handler.
-
- If address is specified as a string, a UNIX socket is used. To log to a
- local syslogd, "WebsockifySysLogHandler(address="/dev/log")" can be
- used. If facility is not specified, LOG_USER is used. If socktype is
- specified as socket.SOCK_DGRAM or socket.SOCK_STREAM, that specific
- socket type will be used. For Unix sockets, you can also specify a
- socktype of None, in which case socket.SOCK_DGRAM will be used, falling
- back to socket.SOCK_STREAM. If ident is specified, this string will be
- used as the application name in all messages sent. Set legacy to True
- to use the old version of the protocol.
- """
-
- self.ident = ident
-
- if legacy:
- self._legacy = True
- self._head_fmt = self._legacy_head_fmt
-
- super().__init__(address, facility, socktype)
-
-
- def emit(self, record):
- """
- Emit a record.
-
- The record is formatted, and then sent to the syslog server. If
- exception information is present, it is NOT sent to the server.
- """
-
- try:
- # Gather info.
- text = self.format(record).replace(self._tail, ' ')
- if not text: # nothing to log
- return
-
- pri = self.encodePriority(self.facility,
- self.mapPriority(record.levelname))
-
- timestamp = time.strftime(self._timestamp_fmt, time.gmtime());
-
- hostname = socket.gethostname()[:self._max_hostname]
-
- if self.ident:
- ident = self.ident[:self._max_ident]
- else:
- ident = ''
-
- pid = os.getpid() # shouldn't need truncation
-
- # Format the header.
- head = {
- 'pri': pri,
- 'timestamp': timestamp,
- 'hostname': hostname,
- 'ident': ident,
- 'pid': pid,
- }
- msg = self._head_fmt.format(**head).encode('ascii', 'ignore')
-
- # Encode text as plain ASCII if possible, else use UTF-8 with BOM.
- try:
- msg += text.encode('ascii')
- except UnicodeEncodeError:
- msg += text.encode('utf-8-sig')
-
- # Add length or tail character, if necessary.
- if self.socktype != socket.SOCK_DGRAM:
- if self._send_length:
- msg = ('%d ' % len(msg)).encode('ascii') + msg
- else:
- msg += self._tail.encode('ascii')
-
- # Send the message.
- if self.unixsocket:
- try:
- self.socket.send(msg)
- except socket.error:
- self._connect_unixsocket(self.address)
- self.socket.send(msg)
-
- else:
- if self.socktype == socket.SOCK_DGRAM:
- self.socket.sendto(msg, self.address)
- else:
- self.socket.sendall(msg)
-
- except (KeyboardInterrupt, SystemExit):
- raise
- except:
- self.handleError(record)
+import logging.handlers as handlers, socket, os, time
+
+
+class WebsockifySysLogHandler(handlers.SysLogHandler):
+ """
+ A handler class that sends proper Syslog-formatted messages,
+ as defined by RFC 5424.
+ """
+
+ _legacy_head_fmt = '<{pri}>{ident}[{pid}]: '
+ _rfc5424_head_fmt = '<{pri}>1 {timestamp} {hostname} {ident} {pid} - - '
+ _head_fmt = _rfc5424_head_fmt
+ _legacy = False
+ _timestamp_fmt = '%Y-%m-%dT%H:%M:%SZ'
+ _max_hostname = 255
+ _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):
+ """
+ Initialize a handler.
+
+ If address is specified as a string, a UNIX socket is used. To log to a
+ local syslogd, "WebsockifySysLogHandler(address="/dev/log")" can be
+ used. If facility is not specified, LOG_USER is used. If socktype is
+ specified as socket.SOCK_DGRAM or socket.SOCK_STREAM, that specific
+ socket type will be used. For Unix sockets, you can also specify a
+ socktype of None, in which case socket.SOCK_DGRAM will be used, falling
+ back to socket.SOCK_STREAM. If ident is specified, this string will be
+ used as the application name in all messages sent. Set legacy to True
+ to use the old version of the protocol.
+ """
+
+ self.ident = ident
+
+ if legacy:
+ self._legacy = True
+ self._head_fmt = self._legacy_head_fmt
+
+ super().__init__(address, facility, socktype)
+
+
+ def emit(self, record):
+ """
+ Emit a record.
+
+ The record is formatted, and then sent to the syslog server. If
+ exception information is present, it is NOT sent to the server.
+ """
+
+ try:
+ # Gather info.
+ text = self.format(record).replace(self._tail, ' ')
+ if not text: # nothing to log
+ return
+
+ pri = self.encodePriority(self.facility,
+ self.mapPriority(record.levelname))
+
+ timestamp = time.strftime(self._timestamp_fmt, time.gmtime());
+
+ hostname = socket.gethostname()[:self._max_hostname]
+
+ if self.ident:
+ ident = self.ident[:self._max_ident]
+ else:
+ ident = ''
+
+ pid = os.getpid() # shouldn't need truncation
+
+ # Format the header.
+ head = {
+ 'pri': pri,
+ 'timestamp': timestamp,
+ 'hostname': hostname,
+ 'ident': ident,
+ 'pid': pid,
+ }
+ msg = self._head_fmt.format(**head).encode('ascii', 'ignore')
+
+ # Encode text as plain ASCII if possible, else use UTF-8 with BOM.
+ try:
+ msg += text.encode('ascii')
+ except UnicodeEncodeError:
+ msg += text.encode('utf-8-sig')
+
+ # Add length or tail character, if necessary.
+ if self.socktype != socket.SOCK_DGRAM:
+ if self._send_length:
+ msg = ('%d ' % len(msg)).encode('ascii') + msg
+ else:
+ msg += self._tail.encode('ascii')
+
+ # Send the message.
+ if self.unixsocket:
+ try:
+ self.socket.send(msg)
+ except OSError:
+ self._connect_unixsocket(self.address)
+ self.socket.send(msg)
+
+ else:
+ if self.socktype == socket.SOCK_DGRAM:
+ self.socket.sendto(msg, self.address)
+ else:
+ self.socket.sendall(msg)
+
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ self.handleError(record)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/websockify-0.12.0/websockify/token_plugins.py
new/websockify-0.13.0/websockify/token_plugins.py
--- old/websockify-0.12.0/websockify/token_plugins.py 2024-06-03
14:40:01.000000000 +0200
+++ new/websockify-0.13.0/websockify/token_plugins.py 2025-02-12
11:45:01.000000000 +0100
@@ -7,6 +7,24 @@
logger = logging.getLogger(__name__)
+_SOURCE_SPLIT_REGEX = re.compile(
+ r'(?<=^)"([^"]+)"(?=:|$)'
+ r'|(?<=:)"([^"]+)"(?=:|$)'
+ r'|(?<=^)([^:]*)(?=:|$)'
+ r'|(?<=:)([^:]*)(?=:|$)',
+)
+
+
+def parse_source_args(src):
+ """It works like src.split(":") but with the ability to use a colon
+ if you wrap the word in quotation marks.
+
+ a:b:c:d -> ['a', 'b', 'c', 'd'
+ a:"b:c":c -> ['a', 'b:c', 'd']
+ """
+ matches = _SOURCE_SPLIT_REGEX.findall(src)
+ return [m[0] or m[1] or m[2] or m[3] for m in matches]
+
class BasePlugin():
def __init__(self, src):
@@ -37,7 +55,7 @@
for line in [l.strip() for l in open(f).readlines()]:
if line and not line.startswith('#'):
try:
- tok, target = re.split(':\s', line)
+ tok, target = re.split(r':\s', line)
self._targets[tok] = target.strip().rsplit(':', 1)
except ValueError:
logger.error("Syntax error in %s on line %d" %
(self.source, index))
@@ -178,9 +196,9 @@
The token source is in the format:
- host[:port[:db[:password]]]
+ host[:port[:db[:password[:namespace]]]]
- where port, db and password are optional. If port or db are left empty
+ where port, db, password and namespace are optional. If port or db are
left empty
they will take its default value, ie. 6379 and 0 respectively.
If your redis server is using the default port (6379) then you can use:
@@ -192,9 +210,18 @@
my-redis-host:::verysecretpass
+ You can also specify a namespace. In this case, the tokens
+ will be stored in the format '{namespace}:{token}'
+
+ my-redis-host::::my-app-namespace
+
+ Or if your namespace is nested, you can wrap it in quotes:
+
+ my-redis-host::::"first-ns:second-ns"
+
In the more general case you will use:
- my-redis-host:6380:1:verysecretpass
+ my-redis-host:6380:1:verysecretpass:my-app-namespace
The TokenRedis plugin expects the format of the target in one of these two
formats:
@@ -234,8 +261,9 @@
self._port = 6379
self._db = 0
self._password = None
+ self._namespace = ""
try:
- fields = src.split(":")
+ fields = parse_source_args(src)
if len(fields) == 1:
self._server = fields[0]
elif len(fields) == 2:
@@ -256,15 +284,28 @@
self._db = 0
if not self._password:
self._password = None
+ elif len(fields) == 5:
+ self._server, self._port, self._db, self._password,
self._namespace = fields
+ if not self._port:
+ self._port = 6379
+ if not self._db:
+ self._db = 0
+ if not self._password:
+ self._password = None
+ if not self._namespace:
+ self._namespace = ""
else:
raise ValueError
self._port = int(self._port)
self._db = int(self._db)
- logger.info("TokenRedis backend initilized (%s:%s)" %
+ if self._namespace:
+ self._namespace += ":"
+
+ logger.info("TokenRedis backend initialized (%s:%s)" %
(self._server, self._port))
except ValueError:
logger.error("The provided --token-source='%s' is not in the "
- "expected format <host>[:<port>[:<db>[:<password>]]]"
%
+ "expected format
<host>[:<port>[:<db>[:<password>[:<namespace>]]]]" %
src)
sys.exit()
@@ -278,7 +319,7 @@
logger.info("resolving token '%s'" % token)
client = redis.Redis(host=self._server, port=self._port,
db=self._db, password=self._password)
- stuff = client.get(token)
+ stuff = client.get(self._namespace + token)
if stuff is None:
return None
else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/websockify-0.12.0/websockify/websocket.py
new/websockify-0.13.0/websockify/websocket.py
--- old/websockify-0.12.0/websockify/websocket.py 2024-06-03
14:40:01.000000000 +0200
+++ new/websockify-0.13.0/websockify/websocket.py 2025-02-12
11:45:01.000000000 +0100
@@ -36,7 +36,7 @@
class WebSocketWantWriteError(ssl.SSLWantWriteError):
pass
-class WebSocket(object):
+class WebSocket:
"""WebSocket protocol socket like class.
This provides access to the WebSocket protocol by behaving much
@@ -139,7 +139,9 @@
self.socket = socket.create_connection((uri.hostname, port))
if uri.scheme in ("wss", "https"):
- self.socket = ssl.wrap_socket(self.socket)
+ context = ssl.create_default_context()
+ self.socket = context.wrap_socket(self.socket,
+ server_hostname=uri.hostname)
self._state = "ssl_handshake"
else:
self._state = "headers"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/websockify-0.12.0/websockify/websocketproxy.py
new/websockify-0.13.0/websockify/websocketproxy.py
--- old/websockify-0.12.0/websockify/websocketproxy.py 2024-06-03
14:40:01.000000000 +0200
+++ new/websockify-0.13.0/websockify/websocketproxy.py 2025-02-12
11:45:01.000000000 +0100
@@ -60,6 +60,12 @@
if not self.server.auth_plugin:
return
+ # clear out any existing SSL_ headers that the client might
+ # have maliciously set
+ ssl_headers = [ h for h in self.headers if h.startswith('SSL_') ]
+ for h in ssl_headers:
+ del self.headers[h]
+
try:
# get client certificate data
client_cert_data = self.request.getpeercert()
@@ -198,7 +204,7 @@
if cqueue or c_pend: wlist.append(self.request)
try:
ins, outs, excepts = select.select(rlist, wlist, [], 1)
- except (select.error, OSError):
+ except OSError:
exc = sys.exc_info()[1]
if hasattr(exc, 'errno'):
err = exc.errno
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/websockify-0.12.0/websockify/websockifyserver.py
new/websockify-0.13.0/websockify/websockifyserver.py
--- old/websockify-0.12.0/websockify/websockifyserver.py 2024-06-03
14:40:01.000000000 +0200
+++ new/websockify-0.13.0/websockify/websockifyserver.py 2025-02-12
11:45:01.000000000 +0100
@@ -187,11 +187,11 @@
""" Send a WebSocket orderly close frame. """
self.request.shutdown(socket.SHUT_RDWR, code, reason)
- def send_pong(self, data=''.encode('ascii')):
+ def send_pong(self, data=b''):
""" Send a WebSocket pong frame. """
self.request.pong(data)
- def send_ping(self, data=''.encode('ascii')):
+ def send_ping(self, data=b''):
""" Send a WebSocket ping frame. """
self.request.ping(data)
@@ -470,7 +470,8 @@
if connect:
sock.connect(addrs[0][4])
if use_ssl:
- sock = ssl.wrap_socket(sock)
+ context = ssl.create_default_context()
+ sock = context.wrap_socket(sock, server_hostname=host)
else:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(addrs[0][4])
@@ -718,20 +719,25 @@
be overridden) for each new client connection.
"""
- if self.listen_fd != None:
- lsock = socket.fromfd(self.listen_fd, socket.AF_INET,
socket.SOCK_STREAM)
- elif self.unix_listen != None:
- lsock = self.socket(host=None,
- unix_socket=self.unix_listen,
- unix_socket_mode=self.unix_listen_mode,
- unix_socket_listen=True)
- else:
- lsock = self.socket(self.listen_host, self.listen_port, False,
- self.prefer_ipv6,
- tcp_keepalive=self.tcp_keepalive,
- tcp_keepcnt=self.tcp_keepcnt,
- tcp_keepidle=self.tcp_keepidle,
- tcp_keepintvl=self.tcp_keepintvl)
+ try:
+ if self.listen_fd != None:
+ lsock = socket.fromfd(self.listen_fd, socket.AF_INET,
socket.SOCK_STREAM)
+ elif self.unix_listen != None:
+ lsock = self.socket(host=None,
+ unix_socket=self.unix_listen,
+ unix_socket_mode=self.unix_listen_mode,
+ unix_socket_listen=True)
+ else:
+ lsock = self.socket(self.listen_host, self.listen_port, False,
+ self.prefer_ipv6,
+ tcp_keepalive=self.tcp_keepalive,
+ tcp_keepcnt=self.tcp_keepcnt,
+ tcp_keepidle=self.tcp_keepidle,
+ tcp_keepintvl=self.tcp_keepintvl)
+ except OSError as e:
+ self.msg("Openening socket failed: %s", str(e))
+ self.vmsg("exception", exc_info=True)
+ sys.exit()
if self.daemon:
keepfd = self.get_log_fd()