Skip to content

Commit

Permalink
Don't log defect warnings on multipart/* responses
Browse files Browse the repository at this point in the history
httplib expects the response body to be available to the HTTP header parser but that's not the case for `parse_headers()` so the following defects are always added to multipart/* responses. We simply ignore these defects at this stage as they are a known issue.
  • Loading branch information
badcure committed Sep 3, 2020
1 parent b83cc46 commit ddc28ad
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 0 deletions.
20 changes: 20 additions & 0 deletions src/urllib3/util/response.py
@@ -1,4 +1,5 @@
from __future__ import absolute_import
from email.errors import StartBoundaryNotFoundDefect, MultipartInvariantViolationDefect
from ..packages.six.moves import http_client as httplib

from ..exceptions import HeaderParsingError
Expand Down Expand Up @@ -66,6 +67,25 @@ def assert_header_parsing(headers):

if isinstance(payload, (bytes, str)):
unparsed_data = payload
if defects:
# httplib is assuming a response body is available
# when parsing headers even when httplib only sends
# header data to parse_headers() This results in
# defects on multipart responses in particular.
# See: https://github.com/urllib3/urllib3/issues/800

# So we ignore the following defects:
# - StartBoundaryNotFoundDefect:
# The claimed start boundary was never found.
# - MultipartInvariantViolationDefect:
# A message claimed to be a multipart but no subparts were found.
defects = [
defect
for defect in defects
if not isinstance(
defect, (StartBoundaryNotFoundDefect, MultipartInvariantViolationDefect)
)
]

if defects or unparsed_data:
raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data)
Expand Down
14 changes: 14 additions & 0 deletions test/test_util.py
Expand Up @@ -748,6 +748,20 @@ def test_assert_header_parsing_throws_typeerror_with_non_headers(self, headers):
with pytest.raises(TypeError):
assert_header_parsing(headers)

@onlyPy3
def test_assert_header_parsing_no_error_on_multipart(self):
from http import client

header_msg = io.BytesIO()
header_msg.write(
b'Content-Type: multipart/encrypted;protocol="application/'
b'HTTP-SPNEGO-session-encrypted";boundary="Encrypted Boundary"'
b"\nServer: Microsoft-HTTPAPI/2.0\nDate: Fri, 16 Aug 2019 19:28:01 GMT"
b"\nContent-Length: 1895\n\n\n"
)
header_msg.seek(0)
assert_header_parsing(client.parse_headers(header_msg))


class TestUtilSSL(object):
"""Test utils that use an SSL backend."""
Expand Down
37 changes: 37 additions & 0 deletions test/with_dummyserver/test_socketlevel.py
Expand Up @@ -1808,3 +1808,40 @@ def socket_handler(listener):
) as pool:
pool.urlopen("GET", "/not_found", preload_content=False)
assert pool.num_connections == 1


class TestMultipartResponse(SocketDummyServerTestCase):
def test_multipart_assert_header_parsing_no_defects(self):
def socket_handler(listener):
for _ in range(2):
sock = listener.accept()[0]
while not sock.recv(65536).endswith(b"\r\n\r\n"):
pass

sock.sendall(
b"HTTP/1.1 404 Not Found\r\n"
b"Server: example.com\r\n"
b"Content-Type: multipart/mixed; boundary=36eeb8c4e26d842a\r\n"
b"Content-Length: 73\r\n"
b"\r\n"
b"--36eeb8c4e26d842a\r\n"
b"Content-Type: text/plain\r\n"
b"\r\n"
b"1\r\n"
b"--36eeb8c4e26d842a--\r\n",
)
sock.close()

self._start_server(socket_handler)
from urllib3.connectionpool import log

with mock.patch.object(log, "warning") as log_warning:
with HTTPConnectionPool(self.host, self.port, timeout=3) as pool:
resp = pool.urlopen("GET", "/")
assert resp.status == 404
assert (
resp.headers["content-type"]
== "multipart/mixed; boundary=36eeb8c4e26d842a"
)
assert len(resp.data) == 73
log_warning.assert_not_called()

0 comments on commit ddc28ad

Please sign in to comment.