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()

Reply via email to