Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Doc/library/urllib.parse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,9 @@

.. [1] Depending on the value of the *missing_as_none* argument.

Reading the :attr:`port` attribute will raise a :exc:`ValueError` if
an invalid port is specified in the URL. See section
An invalid port specified in the URL will raise a :exc:`ValueError`.

Check warning on line 165 in Doc/library/urllib.parse.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:attr reference target not found: port [ref.attr]
Reading the :attr:`port` attribute of a result object created directly
will raise a :exc:`ValueError` too. See section
:ref:`urlparse-result-object` for more information on the result object.

Unmatched square brackets in the :attr:`netloc` attribute will raise a
Expand Down Expand Up @@ -225,6 +226,11 @@
.. versionchanged:: 3.15
Added the *missing_as_none* parameter.

.. versionchanged:: next
An invalid port now raises :exc:`ValueError` when the URL is parsed,

Check warning on line 230 in Doc/library/urllib.parse.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:attr reference target not found: urllib.parse.SplitResult.port [ref.attr]
not only when the :attr:`~urllib.parse.SplitResult.port` attribute is
read.

.. _WHATWG spec: https://url.spec.whatwg.org/#concept-basic-url-parser


Expand Down
12 changes: 8 additions & 4 deletions Lib/test/test_urllib.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,12 +400,16 @@ def test_url_host_with_newline_header_injection_rejected(self):
host = "localhost\r\nX-injected: header\r\n"
schemeless_url = "//" + host + ":8080/test/?test=a"
try:
InvalidURL = http.client.InvalidURL
with self.assertRaisesRegex(
InvalidURL, r"contain control.*\\r"):
# The URL is rejected when it is parsed, because the injected
# header is not a valid port.
with self.assertRaises(ValueError):
urllib.request.urlopen(f"http:{schemeless_url}")
with self.assertRaisesRegex(InvalidURL, r"contain control.*\\n"):
with self.assertRaises(ValueError):
urllib.request.urlopen(f"https:{schemeless_url}")
# Such host is rejected by http.client as well.
with self.assertRaisesRegex(http.client.InvalidURL,
r"contain control.*\\r"):
http.client.HTTPConnection(host, 8080)
finally:
self.unfakehttp()

Expand Down
34 changes: 26 additions & 8 deletions Lib/test/test_urlparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -917,9 +917,8 @@ def test_urlsplit_attributes(self):

# Verify an illegal port raises ValueError
url = b"HTTP://WWW.PYTHON.ORG:65536/doc/#frag"
p = urllib.parse.urlsplit(url)
with self.assertRaisesRegex(ValueError, "out of range"):
p.port
urllib.parse.urlsplit(url)

def test_urlsplit_remove_unsafe_bytes(self):
# Remove ASCII tabs and newlines from input
Expand Down Expand Up @@ -1029,11 +1028,32 @@ def test_attributes_bad_port(self, bytes, parse, port):
self.skipTest('non-ASCII bytes')
netloc = str_encode(netloc)
url = str_encode(url)
p = parse(url)
self.assertEqual(p.netloc, netloc)
with self.assertRaises(ValueError):
parse(url)
# The port is still checked when it is read from a result
# constructed directly.
if bytes:
p = urllib.parse.SplitResultBytes(b'http', netloc, b'/', b'', b'')
else:
p = urllib.parse.SplitResult('http', netloc, '/', '', '')
with self.assertRaises(ValueError):
p.port

@support.subTests('parse', (urllib.parse.urlsplit, urllib.parse.urlparse))
@support.subTests('netloc', ("::1", "a:b:c", "user@::1", "[::1]:80:80"))
def test_attributes_bad_netloc_port(self, parse, netloc):
"""Check handling of a colon which does not delimit a valid port."""
with self.assertRaises(ValueError):
parse("http://" + netloc + "/")

@support.subTests('parse', (urllib.parse.urlsplit, urllib.parse.urlparse))
@support.subTests('netloc', ("www.example.net", "www.example.net:",
"user:password@www.example.net",
"[::1]", "[::1]:80", "[::1]:"))
def test_attributes_good_port(self, parse, netloc):
"""Check that valid netlocs are not rejected."""
self.assertEqual(parse("http://" + netloc + "/").netloc, netloc)

@support.subTests('bytes', (False, True))
@support.subTests('parse', (urllib.parse.urlsplit, urllib.parse.urlparse))
@support.subTests('scheme', (".", "+", "-", "0", "http&", "६http"))
Expand Down Expand Up @@ -1670,13 +1690,11 @@ def test_splitting_bracketed_hosts(self):

def test_port_casting_failure_message(self):
message = "Port could not be cast to integer value as 'oracle'"
p1 = urllib.parse.urlparse('http://Server=sde; Service=sde:oracle')
with self.assertRaisesRegex(ValueError, message):
p1.port
urllib.parse.urlparse('http://Server=sde; Service=sde:oracle')

p2 = urllib.parse.urlsplit('http://Server=sde; Service=sde:oracle')
with self.assertRaisesRegex(ValueError, message):
p2.port
urllib.parse.urlsplit('http://Server=sde; Service=sde:oracle')

def test_telurl_params(self):
p1 = urllib.parse.urlparse('tel:123-4;phone-context=+1-650-516')
Expand Down
4 changes: 4 additions & 0 deletions Lib/urllib/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,8 @@ def urlparse(url, scheme=None, allow_fragments=True, *, missing_as_none=_MISSING
if query is None: query = ''
if fragment is None: fragment = ''
result = ParseResult(scheme, netloc, url, params, query, fragment)
if netloc and ':' in netloc:
result.port # check that the port is valid
result = _coerce_result(result)
result._keep_empty = missing_as_none
return result
Expand Down Expand Up @@ -586,6 +588,8 @@ def urlsplit(url, scheme=None, allow_fragments=True, *, missing_as_none=_MISSING
if query is None: query = ''
if fragment is None: fragment = ''
result = SplitResult(scheme, netloc, url, query, fragment)
if netloc and ':' in netloc:
result.port # check that the port is valid
result = _coerce_result(result)
result._keep_empty = missing_as_none
return result
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:func:`urllib.parse.urlsplit` and :func:`urllib.parse.urlparse` now raise
:exc:`ValueError` for URLs with an invalid port, instead of failing only when
the ``port`` attribute of the result is read. This also rejects URLs with an
unbracketed IPv6 address, like ``"http://::1/"``.
Loading