Skip to content

Commit 2784e78

Browse files
jaracolarryhastings
authored andcommitted
[3.5] bpo-38216, bpo-36274: Allow subclasses to separately override validation and encoding behavior (GH-16448) (#16475)
* [3.5] bpo-38216, bpo-36274: Allow subclasses to separately override validation and encoding behavior (GH-16448)
1 parent edd9bc9 commit 2784e78

File tree

3 files changed

+54
-11
lines changed

3 files changed

+54
-11
lines changed

Lib/http/client.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -984,20 +984,15 @@ def putrequest(self, method, url, skip_host=False,
984984
else:
985985
raise CannotSendRequest(self.__state)
986986

987-
# Save the method we use, we need it later in the response phase
987+
# Save the method for use later in the response phase
988988
self._method = method
989-
if not url:
990-
url = '/'
991-
# Prevent CVE-2019-9740.
992-
match = _contains_disallowed_url_pchar_re.search(url)
993-
if match:
994-
raise InvalidURL("URL can't contain control characters. {!r} "
995-
"(found at least {!r})".format(url,
996-
match.group()))
989+
990+
url = url or '/'
991+
self._validate_path(url)
992+
997993
request = '%s %s %s' % (method, url, self._http_vsn_str)
998994

999-
# Non-ASCII characters should have been eliminated earlier
1000-
self._output(request.encode('ascii'))
995+
self._output(self._encode_request(request))
1001996

1002997
if self._http_vsn == 11:
1003998
# Issue some standard headers for better HTTP/1.1 compliance
@@ -1075,6 +1070,21 @@ def putrequest(self, method, url, skip_host=False,
10751070
# For HTTP/1.0, the server will assume "not chunked"
10761071
pass
10771072

1073+
def _encode_request(self, request):
1074+
# ASCII also helps prevent CVE-2019-9740.
1075+
return request.encode('ascii')
1076+
1077+
def _validate_path(self, url):
1078+
"""Validate a url for putrequest."""
1079+
# Prevent CVE-2019-9740.
1080+
match = _contains_disallowed_url_pchar_re.search(url)
1081+
if match:
1082+
msg = (
1083+
"URL can't contain control characters. {url!r} "
1084+
"(found at least {matched!r})"
1085+
).format(matched=match.group(), **locals())
1086+
raise InvalidURL(msg)
1087+
10781088
def putheader(self, header, *values):
10791089
"""Send a request header line to the server.
10801090

Lib/test/test_httplib.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,34 @@ def run_server():
986986
thread.join()
987987
self.assertEqual(result, b"proxied data\n")
988988

989+
def test_putrequest_override_validation(self):
990+
"""
991+
It should be possible to override the default validation
992+
behavior in putrequest (bpo-38216).
993+
"""
994+
class UnsafeHTTPConnection(client.HTTPConnection):
995+
def _validate_path(self, url):
996+
pass
997+
998+
conn = UnsafeHTTPConnection('example.com')
999+
conn.sock = FakeSocket('')
1000+
conn.putrequest('GET', '/\x00')
1001+
1002+
def test_putrequest_override_encoding(self):
1003+
"""
1004+
It should be possible to override the default encoding
1005+
to transmit bytes in another encoding even if invalid
1006+
(bpo-36274).
1007+
"""
1008+
class UnsafeHTTPConnection(client.HTTPConnection):
1009+
def _encode_request(self, str_url):
1010+
return str_url.encode('utf-8')
1011+
1012+
conn = UnsafeHTTPConnection('example.com')
1013+
conn.sock = FakeSocket('')
1014+
conn.putrequest('GET', '/☃')
1015+
1016+
9891017
class ExtendedReadTest(TestCase):
9901018
"""
9911019
Test peek(), read1(), readline()
@@ -1110,6 +1138,7 @@ def test_peek_0(self):
11101138
p = self.resp.peek(0)
11111139
self.assertLessEqual(0, len(p))
11121140

1141+
11131142
class ExtendedReadTestChunked(ExtendedReadTest):
11141143
"""
11151144
Test peek(), read1(), readline() in chunked mode
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Allow the rare code that wants to send invalid http requests from the
2+
`http.client` library a way to do so. The fixes for bpo-30458 led to
3+
breakage for some projects that were relying on this ability to test their
4+
own behavior in the face of bad requests.

0 commit comments

Comments
 (0)