diff --git a/docs/latest/.buildinfo b/docs/latest/.buildinfo index 38761a16..cda3a074 100644 --- a/docs/latest/.buildinfo +++ b/docs/latest/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: b911398855f7668d2aa125e82024376d +config: 476de7aba2e9373b12d01844d1987590 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/latest/_static/documentation_options.js b/docs/latest/_static/documentation_options.js index 24568a7a..82994434 100644 --- a/docs/latest/_static/documentation_options.js +++ b/docs/latest/_static/documentation_options.js @@ -1,6 +1,6 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '0.4.0', + VERSION: '0.4.1', LANGUAGE: 'None', COLLAPSE_INDEX: false, FILE_SUFFIX: '.html', diff --git a/docs/latest/genindex.html b/docs/latest/genindex.html index 76d51d5e..4e59b26d 100644 --- a/docs/latest/genindex.html +++ b/docs/latest/genindex.html @@ -9,7 +9,7 @@ - Index — google-resumable-media 0.4.0 documentation + Index — google-resumable-media 0.4.1 documentation @@ -59,7 +59,7 @@
- 0.4.0 + 0.4.1
diff --git a/docs/latest/google.resumable_media.common.html b/docs/latest/google.resumable_media.common.html index ee9c997f..0d32fcf0 100644 --- a/docs/latest/google.resumable_media.common.html +++ b/docs/latest/google.resumable_media.common.html @@ -8,7 +8,7 @@ - google.resumable_media.common — google-resumable-media 0.4.0 documentation + google.resumable_media.common — google-resumable-media 0.4.1 documentation @@ -60,7 +60,7 @@
- 0.4.0 + 0.4.1
diff --git a/docs/latest/google.resumable_media.requests.download.html b/docs/latest/google.resumable_media.requests.download.html index fdc65da6..3ff97211 100644 --- a/docs/latest/google.resumable_media.requests.download.html +++ b/docs/latest/google.resumable_media.requests.download.html @@ -8,7 +8,7 @@ - google.resumable_media.requests.download — google-resumable-media 0.4.0 documentation + google.resumable_media.requests.download — google-resumable-media 0.4.1 documentation @@ -60,7 +60,7 @@
- 0.4.0 + 0.4.1
diff --git a/docs/latest/google.resumable_media.requests.html b/docs/latest/google.resumable_media.requests.html index 27446d3e..3f0c8e9e 100644 --- a/docs/latest/google.resumable_media.requests.html +++ b/docs/latest/google.resumable_media.requests.html @@ -8,7 +8,7 @@ - google.resumable_media.requests — google-resumable-media 0.4.0 documentation + google.resumable_media.requests — google-resumable-media 0.4.1 documentation @@ -60,7 +60,7 @@
- 0.4.0 + 0.4.1
diff --git a/docs/latest/google.resumable_media.requests.upload.html b/docs/latest/google.resumable_media.requests.upload.html index 4017aaac..ac36e9f5 100644 --- a/docs/latest/google.resumable_media.requests.upload.html +++ b/docs/latest/google.resumable_media.requests.upload.html @@ -8,7 +8,7 @@ - google.resumable_media.requests.upload — google-resumable-media 0.4.0 documentation + google.resumable_media.requests.upload — google-resumable-media 0.4.1 documentation @@ -59,7 +59,7 @@
- 0.4.0 + 0.4.1
diff --git a/docs/latest/index.html b/docs/latest/index.html index b5ea52b6..a302ce4d 100644 --- a/docs/latest/index.html +++ b/docs/latest/index.html @@ -8,7 +8,7 @@ - google.resumable_media — google-resumable-media 0.4.0 documentation + google.resumable_media — google-resumable-media 0.4.1 documentation @@ -59,7 +59,7 @@
- 0.4.0 + 0.4.1
diff --git a/docs/latest/objects.inv b/docs/latest/objects.inv index 72cfd126..21480a2b 100644 Binary files a/docs/latest/objects.inv and b/docs/latest/objects.inv differ diff --git a/docs/latest/py-modindex.html b/docs/latest/py-modindex.html index c884f07e..444ac997 100644 --- a/docs/latest/py-modindex.html +++ b/docs/latest/py-modindex.html @@ -8,7 +8,7 @@ - Python Module Index — google-resumable-media 0.4.0 documentation + Python Module Index — google-resumable-media 0.4.1 documentation @@ -61,7 +61,7 @@
- 0.4.0 + 0.4.1
diff --git a/docs/latest/search.html b/docs/latest/search.html index 0ef86b89..851a7069 100644 --- a/docs/latest/search.html +++ b/docs/latest/search.html @@ -8,7 +8,7 @@ - Search — google-resumable-media 0.4.0 documentation + Search — google-resumable-media 0.4.1 documentation @@ -59,7 +59,7 @@
- 0.4.0 + 0.4.1
diff --git a/google/resumable_media/_download.py b/google/resumable_media/_download.py index 6260d742..5d2d10d1 100644 --- a/google/resumable_media/_download.py +++ b/google/resumable_media/_download.py @@ -354,24 +354,36 @@ def _process_response(self, response): self._get_status_code, callback=self._make_invalid, ) - content_length = _helpers.header_required( - response, u"content-length", self._get_headers, callback=self._make_invalid - ) - num_bytes = int(content_length) - _, end_byte, total_bytes = get_range_info( + headers = self._get_headers(response) + response_body = self._get_body(response) + + start_byte, end_byte, total_bytes = get_range_info( response, self._get_headers, callback=self._make_invalid ) - response_body = self._get_body(response) - if len(response_body) != num_bytes: - self._make_invalid() - raise common.InvalidResponse( + + transfer_encoding = headers.get(u"transfer-encoding") + + if transfer_encoding is None: + content_length = _helpers.header_required( response, - u"Response is different size than content-length", - u"Expected", - num_bytes, - u"Received", - len(response_body), + u"content-length", + self._get_headers, + callback=self._make_invalid, ) + num_bytes = int(content_length) + if len(response_body) != num_bytes: + self._make_invalid() + raise common.InvalidResponse( + response, + u"Response is different size than content-length", + u"Expected", + num_bytes, + u"Received", + len(response_body), + ) + else: + # 'content-length' header not allowed with chunked encoding. + num_bytes = end_byte - start_byte + 1 # First update ``bytes_downloaded``. self._bytes_downloaded += num_bytes diff --git a/tests/unit/test__download.py b/tests/unit/test__download.py index a984b876..378751f7 100644 --- a/tests/unit/test__download.py +++ b/tests/unit/test__download.py @@ -128,7 +128,7 @@ def test__process_response(self): # Make sure **not finished** before. assert not download.finished - response = mock.Mock(status_code=int(http_client.OK), spec=[u"status_code"]) + response = mock.Mock(status_code=int(http_client.OK), spec=["status_code"]) ret_val = download._process_response(response) assert ret_val is None # Make sure **finished** after. @@ -141,7 +141,7 @@ def test__process_response_bad_status(self): # Make sure **not finished** before. assert not download.finished response = mock.Mock( - status_code=int(http_client.NOT_FOUND), spec=[u"status_code"] + status_code=int(http_client.NOT_FOUND), spec=["status_code"] ) with pytest.raises(common.InvalidResponse) as exc_info: download._process_response(response) @@ -263,7 +263,7 @@ def _mock_response( content=content, headers=response_headers, status_code=status_code, - spec=[u"content", u"headers", u"status_code"], + spec=["content", "headers", "status_code"], ) def test__prepare_request_already_finished(self): @@ -322,7 +322,8 @@ def test__make_invalid(self): assert download.invalid def test__process_response(self): - chunk_size = 333 + data = b"1234xyztL" * 37 # 9 * 37 == 33 + chunk_size = len(data) stream = io.BytesIO() download = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, stream) _fix_up_virtual(download) @@ -336,7 +337,6 @@ def test__process_response(self): assert download.bytes_downloaded == already assert download.total_bytes is None # Actually call the method to update. - data = b"1234xyztL" * 37 # 9 * 37 == 33 response = self._mock_response( already, already + chunk_size - 1, @@ -351,9 +351,42 @@ def test__process_response(self): assert download.total_bytes == total_bytes assert stream.getvalue() == data + def test__process_response_transfer_encoding(self): + data = b"1234xyztL" * 37 + chunk_size = len(data) + stream = io.BytesIO() + download = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, stream) + _fix_up_virtual(download) + + already = 22 + download._bytes_downloaded = already + total_bytes = 4444 + + # Check internal state before. + assert not download.finished + assert download.bytes_downloaded == already + assert download.total_bytes is None + assert not download.invalid + # Actually call the method to update. + response = self._mock_response( + already, + already + chunk_size - 1, + total_bytes, + content=data, + status_code=int(http_client.PARTIAL_CONTENT), + ) + response.headers[u"transfer-encoding"] = "chunked" + del response.headers[u"content-length"] + download._process_response(response) + # Check internal state after. + assert not download.finished + assert download.bytes_downloaded == already + chunk_size + assert download.total_bytes == total_bytes + assert stream.getvalue() == data + def test__process_response_bad_status(self): chunk_size = 384 - stream = mock.Mock(spec=[u"write"]) + stream = mock.Mock(spec=["write"]) download = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, stream) _fix_up_virtual(download) @@ -394,9 +427,10 @@ def test__process_response_missing_content_length(self): assert not download.invalid # Actually call the method to update. response = mock.Mock( - headers={}, + headers={u"content-range": u"bytes 0-99/99"}, status_code=int(http_client.PARTIAL_CONTENT), - spec=[u"headers", u"status_code"], + content=b"DEADBEEF", + spec=["headers", "status_code", "content"], ) with pytest.raises(common.InvalidResponse) as exc_info: download._process_response(response) @@ -430,7 +464,7 @@ def test__process_response_bad_content_range(self): content=data, headers=headers, status_code=int(http_client.PARTIAL_CONTENT), - spec=[u"content", u"headers", u"status_code"], + spec=["content", "headers", "status_code"], ) with pytest.raises(common.InvalidResponse) as exc_info: download._process_response(response) @@ -447,7 +481,7 @@ def test__process_response_bad_content_range(self): def test__process_response_body_wrong_length(self): chunk_size = 10 - stream = mock.Mock(spec=[u"write"]) + stream = mock.Mock(spec=["write"]) download = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, stream) _fix_up_virtual(download) @@ -544,7 +578,7 @@ def test__process_response_when_reaching_end(self): def test__process_response_when_content_range_is_zero(self): chunk_size = 10 - stream = mock.Mock(spec=[u"write"]) + stream = mock.Mock(spec=["write"]) download = _download.ChunkedDownload(EXAMPLE_URL, chunk_size, stream) _fix_up_virtual(download) @@ -552,7 +586,7 @@ def test__process_response_when_content_range_is_zero(self): headers = {u"content-range": content_range} status_code = http_client.REQUESTED_RANGE_NOT_SATISFIABLE response = mock.Mock( - headers=headers, status_code=status_code, spec=[u"headers", "status_code"] + headers=headers, status_code=status_code, spec=["headers", "status_code"] ) download._process_response(response) stream.write.assert_not_called() @@ -604,7 +638,7 @@ class Test_get_range_info(object): @staticmethod def _make_response(content_range): headers = {u"content-range": content_range} - return mock.Mock(headers=headers, spec=[u"headers"]) + return mock.Mock(headers=headers, spec=["headers"]) def _success_helper(self, **kwargs): content_range = u"Bytes 7-11/42" @@ -644,7 +678,7 @@ def test_failure_with_callback(self): callback.assert_called_once_with() def _missing_header_helper(self, **kwargs): - response = mock.Mock(headers={}, spec=[u"headers"]) + response = mock.Mock(headers={}, spec=["headers"]) with pytest.raises(common.InvalidResponse) as exc_info: _download.get_range_info(response, _get_headers, **kwargs) @@ -667,7 +701,7 @@ class Test__check_for_zero_content_range(object): def _make_response(content_range, status_code): headers = {u"content-range": content_range} return mock.Mock( - headers=headers, status_code=status_code, spec=[u"headers", "status_code"] + headers=headers, status_code=status_code, spec=["headers", "status_code"] ) def test_status_code_416_and_test_content_range_zero_both(self):