Skip to content
This repository was archived by the owner on Jan 13, 2021. It is now read-only.

Commit 62c24e1

Browse files
committed
Also transparently decompress deflate.
1 parent c27a72f commit 62c24e1

File tree

2 files changed

+68
-5
lines changed

2 files changed

+68
-5
lines changed

hyper/http20/response.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,45 @@
99
import zlib
1010

1111

12+
class DeflateDecoder(object):
13+
"""
14+
This is a decoding object that wraps ``zlib`` and is used for decoding
15+
deflated content.
16+
17+
This rationale for the existence of this object is pretty unpleasant.
18+
The HTTP RFC specifies that 'deflate' is a valid content encoding. However,
19+
the spec _meant_ the zlib encoding form. Unfortunately, people who didn't
20+
read the RFC very carefully actually implemented a different form of
21+
'deflate'. Insanely, ``zlib`` handles them using two wbits values. This is
22+
such a mess it's hard to adequately articulate.
23+
24+
This class was lovingly borrowed from the excellent urllib3 library. If you
25+
ever see @shazow, you should probably buy him a drink or something.
26+
"""
27+
def __init__(self):
28+
self._first_try = True
29+
self._data = b''
30+
self._obj = zlib.decompressobj(zlib.MAX_WBITS)
31+
32+
def __getattr__(self, name):
33+
return getattr(self._obj, name)
34+
35+
def decompress(self, data):
36+
if not self._first_try:
37+
return self._obj.decompress(data)
38+
39+
self._data += data
40+
try:
41+
return self._obj.decompress(data)
42+
except zlib.error:
43+
self._first_try = False
44+
self._obj = zlib.decompressobj(-zlib.MAX_WBITS)
45+
try:
46+
return self.decompress(self._data)
47+
finally:
48+
self._data = None
49+
50+
1251
class HTTP20Response(object):
1352
"""
1453
An ``HTTP20Response`` wraps the HTTP/2.0 response from the server. It
@@ -45,7 +84,12 @@ def __init__(self, headers, stream):
4584
# This 16 + MAX_WBITS nonsense is to force gzip. See this
4685
# Stack Overflow answer for more:
4786
# http://stackoverflow.com/a/2695466/1401686
48-
self._decompressobj = zlib.decompressobj(16 + zlib.MAX_WBITS)
87+
if self._headers.get('content-encoding', '') == 'gzip':
88+
self._decompressobj = zlib.decompressobj(16 + zlib.MAX_WBITS)
89+
elif self._headers.get('content-encoding', '') == 'deflate':
90+
self._decompressobj = DeflateDecoder()
91+
else:
92+
self._decompressobj = None
4993

5094
def read(self, amt=None, decode_content=True):
5195
"""
@@ -66,11 +110,12 @@ def read(self, amt=None, decode_content=True):
66110
flush_buffer = True
67111

68112
# We may need to decode the body.
69-
if (decode_content and self.getheader('content-encoding', False)):
113+
if decode_content and self._decompressobj and data:
70114
data = self._decompressobj.decompress(data)
71115

72-
if flush_buffer:
73-
data += self._decompressobj.flush()
116+
# If we're at the end of the request, flush the buffer.
117+
if decode_content and self._decompressobj and flush_buffer:
118+
data += self._decompressobj.flush()
74119

75120
return data
76121

test/test_hyper.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1033,7 +1033,7 @@ def test_status_is_stripped_from_headers(self):
10331033
assert resp.status == 200
10341034
assert resp.getheaders() == []
10351035

1036-
def test_response_transparently_decrypts(self):
1036+
def test_response_transparently_decrypts_gzip(self):
10371037
headers = {':status': '200', 'content-encoding': 'gzip'}
10381038
c = zlib.compressobj(wbits=24)
10391039
body = c.compress(b'this is test data')
@@ -1042,6 +1042,24 @@ def test_response_transparently_decrypts(self):
10421042

10431043
assert resp.read() == b'this is test data'
10441044

1045+
def test_response_transparently_decrypts_real_deflate(self):
1046+
headers = {':status': '200', 'content-encoding': 'deflate'}
1047+
c = zlib.compressobj(wbits=zlib.MAX_WBITS)
1048+
body = c.compress(b'this is test data')
1049+
body += c.flush()
1050+
resp = HTTP20Response(headers, DummyStream(body))
1051+
1052+
assert resp.read() == b'this is test data'
1053+
1054+
def test_response_transparently_decrypts_wrong_deflate(self):
1055+
headers = {':status': '200', 'content-encoding': 'deflate'}
1056+
c = zlib.compressobj(wbits=-zlib.MAX_WBITS)
1057+
body = c.compress(b'this is test data')
1058+
body += c.flush()
1059+
resp = HTTP20Response(headers, DummyStream(body))
1060+
1061+
assert resp.read() == b'this is test data'
1062+
10451063

10461064
# Some utility classes for the tests.
10471065
class NullEncoder(object):

0 commit comments

Comments
 (0)