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):