- Tools/Demos - gh-129248: The iOS test runner now strips the log prefix from each line output by the test suite. - gh-104400: Fix several bugs in extraction by switching to an AST parser in pygettext. - Tests - gh-129386: Add test.support.reset_code, which can be used to reset various bytecode-level optimizations and local instrumentation for a function. - gh-128474: Disable test_embed test cases that segfault on BOLT instrument binaries. The tests are only disabled when BOLT is enabled. - gh-128003: Add an option --parallel-threads=N to the regression test runner that runs individual tests in multiple threads in parallel in order to find concurrency bugs. Note that most of the test suite is not yet reviewed for thread-safety or annotated with @thread_unsafe when necessary. - Security - gh-105704: When using urllib.parse.urlsplit() and urllib.parse.urlparse() host parsing would not reject domain names containing square brackets ([ and ]). Square brackets are only valid for IPv6 and IPvFuture hosts according to RFC 3986 Section 3.2.2. - gh-126108: Fix a possible NULL pointer dereference in PySys_AddWarnOptionUnicode(). - gh-80222: Fix bug in the folding of quoted strings when flattening an email message using a modern email policy. Previously when a quoted string was folded so OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python314?expand=0&rev=38
112 lines
6.9 KiB
Diff
112 lines
6.9 KiB
Diff
From 6204ab9f989be3841c8c47e1e2cfe6a658fe16d5 Mon Sep 17 00:00:00 2001
|
|
From: Seth Michael Larson <seth@python.org>
|
|
Date: Tue, 28 Jan 2025 14:09:00 -0600
|
|
Subject: [PATCH 1/4] gh-105704: Disallow square brackets ( and ) in domain
|
|
names for parsed URLs
|
|
|
|
---
|
|
Lib/test/test_urlparse.py | 37 +++++++++-
|
|
Lib/urllib/parse.py | 20 ++++-
|
|
Misc/NEWS.d/next/Security/2025-01-28-14-08-03.gh-issue-105704.EnhHxu.rst | 4 +
|
|
3 files changed, 58 insertions(+), 3 deletions(-)
|
|
create mode 100644 Misc/NEWS.d/next/Security/2025-01-28-14-08-03.gh-issue-105704.EnhHxu.rst
|
|
|
|
--- a/Lib/test/test_urlparse.py
|
|
+++ b/Lib/test/test_urlparse.py
|
|
@@ -1412,16 +1412,51 @@ class UrlParseTestCase(unittest.TestCase
|
|
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af::2309::fae7:1234]/Path?Query')
|
|
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af:2309::fae7:1234:2342:438e:192.0.2.146]/Path?Query')
|
|
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@]v6a.ip[/Path')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[v6a.ip]')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[v6a.ip].suffix')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[v6a.ip]/')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[v6a.ip].suffix/')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[v6a.ip]?')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[v6a.ip].suffix?')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]/')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix/')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]?')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix?')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]:a')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix:a')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]:a1')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix:a1')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]:1a')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix:1a')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]:')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix:/')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]:?')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://user@prefix.[v6a.ip]')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://user@[v6a.ip].suffix')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[v6a.ip')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://v6a.ip]')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://]v6a.ip[')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://]v6a.ip')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://v6a.ip[')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[v6a.ip')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://v6a.ip].suffix')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix]v6a.ip[suffix')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix]v6a.ip')
|
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://v6a.ip[suffix')
|
|
|
|
def test_splitting_bracketed_hosts(self):
|
|
- p1 = urllib.parse.urlsplit('scheme://user@[v6a.ip]/path?query')
|
|
+ p1 = urllib.parse.urlsplit('scheme://user@[v6a.ip]:1234/path?query')
|
|
self.assertEqual(p1.hostname, 'v6a.ip')
|
|
self.assertEqual(p1.username, 'user')
|
|
self.assertEqual(p1.path, '/path')
|
|
+ self.assertEqual(p1.port, 1234)
|
|
p2 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7%test]/path?query')
|
|
self.assertEqual(p2.hostname, '0439:23af:2309::fae7%test')
|
|
self.assertEqual(p2.username, 'user')
|
|
self.assertEqual(p2.path, '/path')
|
|
+ self.assertIs(p2.port, None)
|
|
p3 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7:1234:192.0.2.146%test]/path?query')
|
|
self.assertEqual(p3.hostname, '0439:23af:2309::fae7:1234:192.0.2.146%test')
|
|
self.assertEqual(p3.username, 'user')
|
|
--- a/Lib/urllib/parse.py
|
|
+++ b/Lib/urllib/parse.py
|
|
@@ -439,6 +439,23 @@ def _checknetloc(netloc):
|
|
raise ValueError("netloc '" + netloc + "' contains invalid " +
|
|
"characters under NFKC normalization")
|
|
|
|
+def _check_bracketed_netloc(netloc):
|
|
+ # Note that this function must mirror the splitting
|
|
+ # done in NetlocResultMixins._hostinfo().
|
|
+ hostname_and_port = netloc.rpartition('@')[2]
|
|
+ before_bracket, have_open_br, bracketed = hostname_and_port.partition('[')
|
|
+ if have_open_br:
|
|
+ # No data is allowed before a bracket.
|
|
+ if before_bracket:
|
|
+ raise ValueError("Invalid IPv6 URL")
|
|
+ hostname, _, port = bracketed.partition(']')
|
|
+ # No data is allowed after the bracket but before the port delimiter.
|
|
+ if port and not port.startswith(":"):
|
|
+ raise ValueError("Invalid IPv6 URL")
|
|
+ else:
|
|
+ hostname, _, port = hostname_and_port.partition(':')
|
|
+ _check_bracketed_host(hostname)
|
|
+
|
|
# Valid bracketed hosts are defined in
|
|
# https://www.rfc-editor.org/rfc/rfc3986#page-49 and https://url.spec.whatwg.org/
|
|
def _check_bracketed_host(hostname):
|
|
@@ -505,8 +522,7 @@ def _urlsplit(url, scheme=None, allow_fr
|
|
(']' in netloc and '[' not in netloc)):
|
|
raise ValueError("Invalid IPv6 URL")
|
|
if '[' in netloc and ']' in netloc:
|
|
- bracketed_host = netloc.partition('[')[2].partition(']')[0]
|
|
- _check_bracketed_host(bracketed_host)
|
|
+ _check_bracketed_netloc(netloc)
|
|
if allow_fragments and '#' in url:
|
|
url, fragment = url.split('#', 1)
|
|
if '?' in url:
|
|
--- /dev/null
|
|
+++ b/Misc/NEWS.d/next/Security/2025-01-28-14-08-03.gh-issue-105704.EnhHxu.rst
|
|
@@ -0,0 +1,4 @@
|
|
+When using :func:`urllib.parse.urlsplit` and :func:`urllib.parse.urlparse` host
|
|
+parsing would not reject domain names containing square brackets (``[`` and
|
|
+``]``). Square brackets are only valid for IPv6 and IPvFuture hosts according to
|
|
+`RFC 3986 Section 3.2.2 <https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2>`__.
|