|
6 | 6 |
|
7 | 7 | from .util import CaseInsensitiveDict |
8 | 8 |
|
| 9 | +try: |
| 10 | + # This supports both brotli & brotlipy packages |
| 11 | + import brotli |
| 12 | +except ImportError: |
| 13 | + try: |
| 14 | + import brotlicffi as brotli |
| 15 | + except ImportError: |
| 16 | + brotli = None |
| 17 | + |
| 18 | + |
| 19 | +def decompress_deflate(body): |
| 20 | + try: |
| 21 | + return zlib.decompress(body) |
| 22 | + except zlib.error: |
| 23 | + # Assume the response was already decompressed |
| 24 | + return body |
| 25 | + |
| 26 | + |
| 27 | +def decompress_gzip(body): |
| 28 | + # To (de-)compress gzip format, use wbits = zlib.MAX_WBITS | 16. |
| 29 | + try: |
| 30 | + return zlib.decompress(body, zlib.MAX_WBITS | 16) |
| 31 | + except zlib.error: |
| 32 | + # Assume the response was already decompressed |
| 33 | + return body |
| 34 | + |
| 35 | + |
| 36 | +AVAILABLE_DECOMPRESSORS = { |
| 37 | + "deflate": decompress_deflate, |
| 38 | + "gzip": decompress_gzip, |
| 39 | +} |
| 40 | + |
| 41 | +if brotli is not None: |
| 42 | + |
| 43 | + def decompress_brotli(body): |
| 44 | + try: |
| 45 | + return brotli.decompress(body) |
| 46 | + except brotli.error: |
| 47 | + # Assume the response was already decompressed |
| 48 | + return body |
| 49 | + |
| 50 | + AVAILABLE_DECOMPRESSORS["br"] = decompress_brotli |
| 51 | + |
9 | 52 |
|
10 | 53 | def replace_headers(request, replacements): |
11 | 54 | """Replace headers in request according to replacements. |
@@ -136,45 +179,30 @@ def remove_post_data_parameters(request, post_data_parameters_to_remove): |
136 | 179 |
|
137 | 180 | def decode_response(response): |
138 | 181 | """ |
139 | | - If the response is compressed with gzip or deflate: |
| 182 | + If the response is compressed with any supported compression (gzip, |
| 183 | + deflate, br if available): |
140 | 184 | 1. decompress the response body |
141 | 185 | 2. delete the content-encoding header |
142 | 186 | 3. update content-length header to decompressed length |
143 | 187 | """ |
144 | 188 |
|
145 | | - def is_compressed(headers): |
146 | | - encoding = headers.get("content-encoding", []) |
147 | | - return encoding and encoding[0] in ("gzip", "deflate") |
148 | | - |
149 | | - def decompress_body(body, encoding): |
150 | | - """Returns decompressed body according to encoding using zlib. |
151 | | - to (de-)compress gzip format, use wbits = zlib.MAX_WBITS | 16 |
152 | | - """ |
153 | | - if not body: |
154 | | - return "" |
155 | | - if encoding == "gzip": |
156 | | - try: |
157 | | - return zlib.decompress(body, zlib.MAX_WBITS | 16) |
158 | | - except zlib.error: |
159 | | - return body # assumes that the data was already decompressed |
160 | | - else: # encoding == 'deflate' |
161 | | - try: |
162 | | - return zlib.decompress(body) |
163 | | - except zlib.error: |
164 | | - return body # assumes that the data was already decompressed |
165 | | - |
166 | 189 | # Deepcopy here in case `headers` contain objects that could |
167 | 190 | # be mutated by a shallow copy and corrupt the real response. |
168 | 191 | response = copy.deepcopy(response) |
169 | 192 | headers = CaseInsensitiveDict(response["headers"]) |
170 | | - if is_compressed(headers): |
171 | | - encoding = headers["content-encoding"][0] |
172 | | - headers["content-encoding"].remove(encoding) |
173 | | - if not headers["content-encoding"]: |
174 | | - del headers["content-encoding"] |
175 | | - |
176 | | - new_body = decompress_body(response["body"]["string"], encoding) |
177 | | - response["body"]["string"] = new_body |
178 | | - headers["content-length"] = [str(len(new_body))] |
179 | | - response["headers"] = dict(headers) |
| 193 | + content_encoding = headers.get("content-encoding") |
| 194 | + if not content_encoding: |
| 195 | + return response |
| 196 | + decompressor = AVAILABLE_DECOMPRESSORS.get(content_encoding[0]) |
| 197 | + if not decompressor: |
| 198 | + return response |
| 199 | + |
| 200 | + headers["content-encoding"].remove(content_encoding[0]) |
| 201 | + if not headers["content-encoding"]: |
| 202 | + del headers["content-encoding"] |
| 203 | + |
| 204 | + new_body = decompressor(response["body"]["string"]) |
| 205 | + response["body"]["string"] = new_body |
| 206 | + headers["content-length"] = [str(len(new_body))] |
| 207 | + response["headers"] = dict(headers) |
180 | 208 | return response |
0 commit comments