| 1 | # SPDX-License-Identifier: MIT |
| 2 | from __future__ import absolute_import |
| 3 | from contextlib import contextmanager |
| 4 | import zlib |
| 5 | import io |
| 6 | import logging |
| 7 | from socket import timeout as SocketTimeout |
| 8 | from socket import error as SocketError |
| 9 | |
| 10 | from ._collections import HTTPHeaderDict |
| 11 | from .exceptions import ( |
| 12 | BodyNotHttplibCompatible, ProtocolError, DecodeError, ReadTimeoutError, |
| 13 | ResponseNotChunked, IncompleteRead, InvalidHeader |
| 14 | ) |
| 15 | from .packages.six import string_types as basestring, binary_type, PY3 |
| 16 | from .packages.six.moves import http_client as httplib |
| 17 | from .connection import HTTPException, BaseSSLError |
| 18 | from .util.response import is_fp_closed, is_response_to_head |
| 19 | |
| 20 | log = logging.getLogger(__name__) |
| 21 | |
| 22 | |
| 23 | class DeflateDecoder(object): |
| 24 | |
| 25 | def __init__(self): |
| 26 | self._first_try = True |
| 27 | self._data = binary_type() |
| 28 | self._obj = zlib.decompressobj() |
| 29 | |
| 30 | def __getattr__(self, name): |
| 31 | return getattr(self._obj, name) |
| 32 | |
| 33 | def decompress(self, data): |
| 34 | if not data: |
| 35 | return data |
| 36 | |
| 37 | if not self._first_try: |
| 38 | return self._obj.decompress(data) |
| 39 | |
| 40 | self._data += data |
| 41 | try: |
| 42 | decompressed = self._obj.decompress(data) |
| 43 | if decompressed: |
| 44 | self._first_try = False |
| 45 | self._data = None |
| 46 | return decompressed |
| 47 | except zlib.error: |
| 48 | self._first_try = False |
| 49 | self._obj = zlib.decompressobj(-zlib.MAX_WBITS) |
| 50 | try: |
| 51 | return self.decompress(self._data) |
| 52 | finally: |
| 53 | self._data = None |
| 54 | |
| 55 | |
| 56 | class GzipDecoder(object): |
| 57 | |
| 58 | def __init__(self): |
| 59 | self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) |
| 60 | |
| 61 | def __getattr__(self, name): |
| 62 | return getattr(self._obj, name) |
| 63 | |
| 64 | def decompress(self, data): |
| 65 | if not data: |
| 66 | return data |
| 67 | return self._obj.decompress(data) |
| 68 | |
| 69 | |
| 70 | def _get_decoder(mode): |
| 71 | if mode == 'gzip': |
| 72 | return GzipDecoder() |
| 73 | |
| 74 | return DeflateDecoder() |
| 75 | |
| 76 | |
| 77 | class HTTPResponse(io.IOBase): |
| 78 | """ |
| 79 | HTTP Response container. |
| 80 | |
| 81 | Backwards-compatible to httplib's HTTPResponse but the response ``body`` is |
| 82 | loaded and decoded on-demand when the ``data`` property is accessed. This |
| 83 | class is also compatible with the Python standard library's :mod:`io` |
| 84 | module, and can hence be treated as a readable object in the context of that |
| 85 | framework. |
| 86 | |
| 87 | Extra parameters for behaviour not present in httplib.HTTPResponse: |
| 88 | |
| 89 | :param preload_content: |
| 90 | If True, the response's body will be preloaded during construction. |
| 91 | |
| 92 | :param decode_content: |
| 93 | If True, attempts to decode specific content-encoding's based on headers |
| 94 | (like 'gzip' and 'deflate') will be skipped and raw data will be used |
| 95 | instead. |
| 96 | |
| 97 | :param original_response: |
| 98 | When this HTTPResponse wrapper is generated from an httplib.HTTPResponse |
| 99 | object, it's convenient to include the original for debug purposes. It's |
| 100 | otherwise unused. |
| 101 | |
| 102 | :param retries: |
| 103 | The retries contains the last :class:`~urllib3.util.retry.Retry` that |
| 104 | was used during the request. |
| 105 | |
| 106 | :param enforce_content_length: |
| 107 | Enforce content length checking. Body returned by server must match |
| 108 | value of Content-Length header, if present. Otherwise, raise error. |
| 109 | """ |
| 110 | |
| 111 | CONTENT_DECODERS = ['gzip', 'deflate'] |
| 112 | REDIRECT_STATUSES = [301, 302, 303, 307, 308] |
| 113 | |
| 114 | def __init__(self, body='', headers=None, status=0, version=0, reason=None, |
| 115 | strict=0, preload_content=True, decode_content=True, |
| 116 | original_response=None, pool=None, connection=None, |
| 117 | retries=None, enforce_content_length=False, request_method=None): |
| 118 | |
| 119 | if isinstance(headers, HTTPHeaderDict): |
| 120 | self.headers = headers |
| 121 | else: |
| 122 | self.headers = HTTPHeaderDict(headers) |
| 123 | self.status = status |
| 124 | self.version = version |
| 125 | self.reason = reason |
| 126 | self.strict = strict |
| 127 | self.decode_content = decode_content |
| 128 | self.retries = retries |
| 129 | self.enforce_content_length = enforce_content_length |
| 130 | |
| 131 | self._decoder = None |
| 132 | self._body = None |
| 133 | self._fp = None |
| 134 | self._original_response = original_response |
| 135 | self._fp_bytes_read = 0 |
| 136 | |
| 137 | if body and isinstance(body, (basestring, binary_type)): |
| 138 | self._body = body |
| 139 | |
| 140 | self._pool = pool |
| 141 | self._connection = connection |
| 142 | |
| 143 | if hasattr(body, 'read'): |
| 144 | self._fp = body |
| 145 | |
| 146 | # Are we using the chunked-style of transfer encoding? |
| 147 | self.chunked = False |
| 148 | self.chunk_left = None |
| 149 | tr_enc = self.headers.get('transfer-encoding', '').lower() |
| 150 | # Don't incur the penalty of creating a list and then discarding it |
| 151 | encodings = (enc.strip() for enc in tr_enc.split(",")) |
| 152 | if "chunked" in encodings: |
| 153 | self.chunked = True |
| 154 | |
| 155 | # Determine length of response |
| 156 | self.length_remaining = self._init_length(request_method) |
| 157 | |
| 158 | # If requested, preload the body. |
| 159 | if preload_content and not self._body: |
| 160 | self._body = self.read(decode_content=decode_content) |
| 161 | |
| 162 | def get_redirect_location(self): |
| 163 | """ |
| 164 | Should we redirect and where to? |
| 165 | |
| 166 | :returns: Truthy redirect location string if we got a redirect status |
| 167 | code and valid location. ``None`` if redirect status and no |
| 168 | location. ``False`` if not a redirect status code. |
| 169 | """ |
| 170 | if self.status in self.REDIRECT_STATUSES: |
| 171 | return self.headers.get('location') |
| 172 | |
| 173 | return False |
| 174 | |
| 175 | def release_conn(self): |
| 176 | if not self._pool or not self._connection: |
| 177 | return |
| 178 | |
| 179 | self._pool._put_conn(self._connection) |
| 180 | self._connection = None |
| 181 | |
| 182 | @property |
| 183 | def data(self): |
| 184 | # For backwords-compat with earlier urllib3 0.4 and earlier. |
| 185 | if self._body: |
| 186 | return self._body |
| 187 | |
| 188 | if self._fp: |
| 189 | return self.read(cache_content=True) |
| 190 | |
| 191 | @property |
| 192 | def connection(self): |
| 193 | return self._connection |
| 194 | |
| 195 | def tell(self): |
| 196 | """ |
| 197 | Obtain the number of bytes pulled over the wire so far. May differ from |
| 198 | the amount of content returned by :meth:``HTTPResponse.read`` if bytes |
| 199 | are encoded on the wire (e.g, compressed). |
| 200 | """ |
| 201 | return self._fp_bytes_read |
| 202 | |
| 203 | def _init_length(self, request_method): |
| 204 | """ |
| 205 | Set initial length value for Response content if available. |
| 206 | """ |
| 207 | length = self.headers.get('content-length') |
| 208 | |
| 209 | if length is not None and self.chunked: |
| 210 | # This Response will fail with an IncompleteRead if it can't be |
| 211 | # received as chunked. This method falls back to attempt reading |
| 212 | # the response before raising an exception. |
| 213 | log.warning("Received response with both Content-Length and " |
| 214 | "Transfer-Encoding set. This is expressly forbidden " |
| 215 | "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " |
| 216 | "attempting to process response as Transfer-Encoding: " |
| 217 | "chunked.") |
| 218 | return None |
| 219 | |
| 220 | elif length is not None: |
| 221 | try: |
| 222 | # RFC 7230 section 3.3.2 specifies multiple content lengths can |
| 223 | # be sent in a single Content-Length header |
| 224 | # (e.g. Content-Length: 42, 42). This line ensures the values |
| 225 | # are all valid ints and that as long as the `set` length is 1, |
| 226 | # all values are the same. Otherwise, the header is invalid. |
| 227 | lengths = set([int(val) for val in length.split(',')]) |
| 228 | if len(lengths) > 1: |
| 229 | raise InvalidHeader("Content-Length contained multiple " |
| 230 | "unmatching values (%s)" % length) |
| 231 | length = lengths.pop() |
| 232 | except ValueError: |
| 233 | length = None |
| 234 | else: |
| 235 | if length < 0: |
| 236 | length = None |
| 237 | |
| 238 | # Convert status to int for comparison |
| 239 | # In some cases, httplib returns a status of "_UNKNOWN" |
| 240 | try: |
| 241 | status = int(self.status) |
| 242 | except ValueError: |
| 243 | status = 0 |
| 244 | |
| 245 | # Check for responses that shouldn't include a body |
| 246 | if status in (204, 304) or 100 <= status < 200 or request_method == 'HEAD': |
| 247 | length = 0 |
| 248 | |
| 249 | return length |
| 250 | |
| 251 | def _init_decoder(self): |
| 252 | """ |
| 253 | Set-up the _decoder attribute if necessary. |
| 254 | """ |
| 255 | # Note: content-encoding value should be case-insensitive, per RFC 7230 |
| 256 | # Section 3.2 |
| 257 | content_encoding = self.headers.get('content-encoding', '').lower() |
| 258 | if self._decoder is None and content_encoding in self.CONTENT_DECODERS: |
| 259 | self._decoder = _get_decoder(content_encoding) |
| 260 | |
| 261 | def _decode(self, data, decode_content, flush_decoder): |
| 262 | """ |
| 263 | Decode the data passed in and potentially flush the decoder. |
| 264 | """ |
| 265 | try: |
| 266 | if decode_content and self._decoder: |
| 267 | data = self._decoder.decompress(data) |
| 268 | except (IOError, zlib.error) as e: |
| 269 | content_encoding = self.headers.get('content-encoding', '').lower() |
| 270 | raise DecodeError( |
| 271 | "Received response with content-encoding: %s, but " |
| 272 | "failed to decode it." % content_encoding, e) |
| 273 | |
| 274 | if flush_decoder and decode_content: |
| 275 | data += self._flush_decoder() |
| 276 | |
| 277 | return data |
| 278 | |
| 279 | def _flush_decoder(self): |
| 280 | """ |
| 281 | Flushes the decoder. Should only be called if the decoder is actually |
| 282 | being used. |
| 283 | """ |
| 284 | if self._decoder: |
| 285 | buf = self._decoder.decompress(b'') |
| 286 | return buf + self._decoder.flush() |
| 287 | |
| 288 | return b'' |
| 289 | |
| 290 | @contextmanager |
| 291 | def _error_catcher(self): |
| 292 | """ |
| 293 | Catch low-level python exceptions, instead re-raising urllib3 |
| 294 | variants, so that low-level exceptions are not leaked in the |
| 295 | high-level api. |
| 296 | |
| 297 | On exit, release the connection back to the pool. |
| 298 | """ |
| 299 | clean_exit = False |
| 300 | |
| 301 | try: |
| 302 | try: |
| 303 | yield |
| 304 | |
| 305 | except SocketTimeout: |
| 306 | # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but |
| 307 | # there is yet no clean way to get at it from this context. |
| 308 | raise ReadTimeoutError(self._pool, None, 'Read timed out.') |
| 309 | |
| 310 | except BaseSSLError as e: |
| 311 | # FIXME: Is there a better way to differentiate between SSLErrors? |
| 312 | if 'read operation timed out' not in str(e): # Defensive: |
| 313 | # This shouldn't happen but just in case we're missing an edge |
| 314 | # case, let's avoid swallowing SSL errors. |
| 315 | raise |
| 316 | |
| 317 | raise ReadTimeoutError(self._pool, None, 'Read timed out.') |
| 318 | |
| 319 | except (HTTPException, SocketError) as e: |
| 320 | # This includes IncompleteRead. |
| 321 | raise ProtocolError('Connection broken: %r' % e, e) |
| 322 | |
| 323 | # If no exception is thrown, we should avoid cleaning up |
| 324 | # unnecessarily. |
| 325 | clean_exit = True |
| 326 | finally: |
| 327 | # If we didn't terminate cleanly, we need to throw away our |
| 328 | # connection. |
| 329 | if not clean_exit: |
| 330 | # The response may not be closed but we're not going to use it |
| 331 | # anymore so close it now to ensure that the connection is |
| 332 | # released back to the pool. |
| 333 | if self._original_response: |
| 334 | self._original_response.close() |
| 335 | |
| 336 | # Closing the response may not actually be sufficient to close |
| 337 | # everything, so if we have a hold of the connection close that |
| 338 | # too. |
| 339 | if self._connection: |
| 340 | self._connection.close() |
| 341 | |
| 342 | # If we hold the original response but it's closed now, we should |
| 343 | # return the connection back to the pool. |
| 344 | if self._original_response and self._original_response.isclosed(): |
| 345 | self.release_conn() |
| 346 | |
| 347 | def read(self, amt=None, decode_content=None, cache_content=False): |
| 348 | """ |
| 349 | Similar to :meth:`httplib.HTTPResponse.read`, but with two additional |
| 350 | parameters: ``decode_content`` and ``cache_content``. |
| 351 | |
| 352 | :param amt: |
| 353 | How much of the content to read. If specified, caching is skipped |
| 354 | because it doesn't make sense to cache partial content as the full |
| 355 | response. |
| 356 | |
| 357 | :param decode_content: |
| 358 | If True, will attempt to decode the body based on the |
| 359 | 'content-encoding' header. |
| 360 | |
| 361 | :param cache_content: |
| 362 | If True, will save the returned data such that the same result is |
| 363 | returned despite of the state of the underlying file object. This |
| 364 | is useful if you want the ``.data`` property to continue working |
| 365 | after having ``.read()`` the file object. (Overridden if ``amt`` is |
| 366 | set.) |
| 367 | """ |
| 368 | self._init_decoder() |
| 369 | if decode_content is None: |
| 370 | decode_content = self.decode_content |
| 371 | |
| 372 | if self._fp is None: |
| 373 | return |
| 374 | |
| 375 | flush_decoder = False |
| 376 | data = None |
| 377 | |
| 378 | with self._error_catcher(): |
| 379 | if amt is None: |
| 380 | # cStringIO doesn't like amt=None |
| 381 | data = self._fp.read() |
| 382 | flush_decoder = True |
| 383 | else: |
| 384 | cache_content = False |
| 385 | data = self._fp.read(amt) |
| 386 | if amt != 0 and not data: # Platform-specific: Buggy versions of Python. |
| 387 | # Close the connection when no data is returned |
| 388 | # |
| 389 | # This is redundant to what httplib/http.client _should_ |
| 390 | # already do. However, versions of python released before |
| 391 | # December 15, 2012 (http://bugs.python.org/issue16298) do |
| 392 | # not properly close the connection in all cases. There is |
| 393 | # no harm in redundantly calling close. |
| 394 | self._fp.close() |
| 395 | flush_decoder = True |
| 396 | if self.enforce_content_length and self.length_remaining not in (0, None): |
| 397 | # This is an edge case that httplib failed to cover due |
| 398 | # to concerns of backward compatibility. We're |
| 399 | # addressing it here to make sure IncompleteRead is |
| 400 | # raised during streaming, so all calls with incorrect |
| 401 | # Content-Length are caught. |
| 402 | raise IncompleteRead(self._fp_bytes_read, self.length_remaining) |
| 403 | |
| 404 | if data: |
| 405 | self._fp_bytes_read += len(data) |
| 406 | if self.length_remaining is not None: |
| 407 | self.length_remaining -= len(data) |
| 408 | |
| 409 | data = self._decode(data, decode_content, flush_decoder) |
| 410 | |
| 411 | if cache_content: |
| 412 | self._body = data |
| 413 | |
| 414 | return data |
| 415 | |
| 416 | def stream(self, amt=2**16, decode_content=None): |
| 417 | """ |
| 418 | A generator wrapper for the read() method. A call will block until |
| 419 | ``amt`` bytes have been read from the connection or until the |
| 420 | connection is closed. |
| 421 | |
| 422 | :param amt: |
| 423 | How much of the content to read. The generator will return up to |
| 424 | much data per iteration, but may return less. This is particularly |
| 425 | likely when using compressed data. However, the empty string will |
| 426 | never be returned. |
| 427 | |
| 428 | :param decode_content: |
| 429 | If True, will attempt to decode the body based on the |
| 430 | 'content-encoding' header. |
| 431 | """ |
| 432 | if self.chunked and self.supports_chunked_reads(): |
| 433 | for line in self.read_chunked(amt, decode_content=decode_content): |
| 434 | yield line |
| 435 | else: |
| 436 | while not is_fp_closed(self._fp): |
| 437 | data = self.read(amt=amt, decode_content=decode_content) |
| 438 | |
| 439 | if data: |
| 440 | yield data |
| 441 | |
| 442 | @classmethod |
| 443 | def from_httplib(ResponseCls, r, **response_kw): |
| 444 | """ |
| 445 | Given an :class:`httplib.HTTPResponse` instance ``r``, return a |
| 446 | corresponding :class:`urllib3.response.HTTPResponse` object. |
| 447 | |
| 448 | Remaining parameters are passed to the HTTPResponse constructor, along |
| 449 | with ``original_response=r``. |
| 450 | """ |
| 451 | headers = r.msg |
| 452 | |
| 453 | if not isinstance(headers, HTTPHeaderDict): |
| 454 | if PY3: # Python 3 |
| 455 | headers = HTTPHeaderDict(headers.items()) |
| 456 | else: # Python 2 |
| 457 | headers = HTTPHeaderDict.from_httplib(headers) |
| 458 | |
| 459 | # HTTPResponse objects in Python 3 don't have a .strict attribute |
| 460 | strict = getattr(r, 'strict', 0) |
| 461 | resp = ResponseCls(body=r, |
| 462 | headers=headers, |
| 463 | status=r.status, |
| 464 | version=r.version, |
| 465 | reason=r.reason, |
| 466 | strict=strict, |
| 467 | original_response=r, |
| 468 | **response_kw) |
| 469 | return resp |
| 470 | |
| 471 | # Backwards-compatibility methods for httplib.HTTPResponse |
| 472 | def getheaders(self): |
| 473 | return self.headers |
| 474 | |
| 475 | def getheader(self, name, default=None): |
| 476 | return self.headers.get(name, default) |
| 477 | |
| 478 | # Overrides from io.IOBase |
| 479 | def close(self): |
| 480 | if not self.closed: |
| 481 | self._fp.close() |
| 482 | |
| 483 | if self._connection: |
| 484 | self._connection.close() |
| 485 | |
| 486 | @property |
| 487 | def closed(self): |
| 488 | if self._fp is None: |
| 489 | return True |
| 490 | elif hasattr(self._fp, 'isclosed'): |
| 491 | return self._fp.isclosed() |
| 492 | elif hasattr(self._fp, 'closed'): |
| 493 | return self._fp.closed |
| 494 | else: |
| 495 | return True |
| 496 | |
| 497 | def fileno(self): |
| 498 | if self._fp is None: |
| 499 | raise IOError("HTTPResponse has no file to get a fileno from") |
| 500 | elif hasattr(self._fp, "fileno"): |
| 501 | return self._fp.fileno() |
| 502 | else: |
| 503 | raise IOError("The file-like object this HTTPResponse is wrapped " |
| 504 | "around has no file descriptor") |
| 505 | |
| 506 | def flush(self): |
| 507 | if self._fp is not None and hasattr(self._fp, 'flush'): |
| 508 | return self._fp.flush() |
| 509 | |
| 510 | def readable(self): |
| 511 | # This method is required for `io` module compatibility. |
| 512 | return True |
| 513 | |
| 514 | def readinto(self, b): |
| 515 | # This method is required for `io` module compatibility. |
| 516 | temp = self.read(len(b)) |
| 517 | if len(temp) == 0: |
| 518 | return 0 |
| 519 | else: |
| 520 | b[:len(temp)] = temp |
| 521 | return len(temp) |
| 522 | |
| 523 | def supports_chunked_reads(self): |
| 524 | """ |
| 525 | Checks if the underlying file-like object looks like a |
| 526 | httplib.HTTPResponse object. We do this by testing for the fp |
| 527 | attribute. If it is present we assume it returns raw chunks as |
| 528 | processed by read_chunked(). |
| 529 | """ |
| 530 | return hasattr(self._fp, 'fp') |
| 531 | |
| 532 | def _update_chunk_length(self): |
| 533 | # First, we'll figure out length of a chunk and then |
| 534 | # we'll try to read it from socket. |
| 535 | if self.chunk_left is not None: |
| 536 | return |
| 537 | line = self._fp.fp.readline() |
| 538 | line = line.split(b';', 1)[0] |
| 539 | try: |
| 540 | self.chunk_left = int(line, 16) |
| 541 | except ValueError: |
| 542 | # Invalid chunked protocol response, abort. |
| 543 | self.close() |
| 544 | raise httplib.IncompleteRead(line) |
| 545 | |
| 546 | def _handle_chunk(self, amt): |
| 547 | returned_chunk = None |
| 548 | if amt is None: |
| 549 | chunk = self._fp._safe_read(self.chunk_left) |
| 550 | returned_chunk = chunk |
| 551 | self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. |
| 552 | self.chunk_left = None |
| 553 | elif amt < self.chunk_left: |
| 554 | value = self._fp._safe_read(amt) |
| 555 | self.chunk_left = self.chunk_left - amt |
| 556 | returned_chunk = value |
| 557 | elif amt == self.chunk_left: |
| 558 | value = self._fp._safe_read(amt) |
| 559 | self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. |
| 560 | self.chunk_left = None |
| 561 | returned_chunk = value |
| 562 | else: # amt > self.chunk_left |
| 563 | returned_chunk = self._fp._safe_read(self.chunk_left) |
| 564 | self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. |
| 565 | self.chunk_left = None |
| 566 | return returned_chunk |
| 567 | |
| 568 | def read_chunked(self, amt=None, decode_content=None): |
| 569 | """ |
| 570 | Similar to :meth:`HTTPResponse.read`, but with an additional |
| 571 | parameter: ``decode_content``. |
| 572 | |
| 573 | :param decode_content: |
| 574 | If True, will attempt to decode the body based on the |
| 575 | 'content-encoding' header. |
| 576 | """ |
| 577 | self._init_decoder() |
| 578 | # FIXME: Rewrite this method and make it a class with a better structured logic. |
| 579 | if not self.chunked: |
| 580 | raise ResponseNotChunked( |
| 581 | "Response is not chunked. " |
| 582 | "Header 'transfer-encoding: chunked' is missing.") |
| 583 | if not self.supports_chunked_reads(): |
| 584 | raise BodyNotHttplibCompatible( |
| 585 | "Body should be httplib.HTTPResponse like. " |
| 586 | "It should have have an fp attribute which returns raw chunks.") |
| 587 | |
| 588 | # Don't bother reading the body of a HEAD request. |
| 589 | if self._original_response and is_response_to_head(self._original_response): |
| 590 | self._original_response.close() |
| 591 | return |
| 592 | |
| 593 | with self._error_catcher(): |
| 594 | while True: |
| 595 | self._update_chunk_length() |
| 596 | if self.chunk_left == 0: |
| 597 | break |
| 598 | chunk = self._handle_chunk(amt) |
| 599 | decoded = self._decode(chunk, decode_content=decode_content, |
| 600 | flush_decoder=False) |
| 601 | if decoded: |
| 602 | yield decoded |
| 603 | |
| 604 | if decode_content: |
| 605 | # On CPython and PyPy, we should never need to flush the |
| 606 | # decoder. However, on Jython we *might* need to, so |
| 607 | # lets defensively do it anyway. |
| 608 | decoded = self._flush_decoder() |
| 609 | if decoded: # Platform-specific: Jython. |
| 610 | yield decoded |
| 611 | |
| 612 | # Chunk content ends with \r\n: discard it. |
| 613 | while True: |
| 614 | line = self._fp.fp.readline() |
| 615 | if not line: |
| 616 | # Some sites may not end with '\r\n'. |
| 617 | break |
| 618 | if line == b'\r\n': |
| 619 | break |
| 620 | |
| 621 | # We read everything; close the "file". |
| 622 | if self._original_response: |
| 623 | self._original_response.close() |