diff --git a/googleapiclient/http.py b/googleapiclient/http.py index 926ca1bcfa9..a87575184a7 100644 --- a/googleapiclient/http.py +++ b/googleapiclient/http.py @@ -1026,13 +1026,17 @@ def next_chunk(self, http=None, num_retries=0): chunk_end = self.resumable_progress + len(data) - 1 headers = { - "Content-Range": "bytes %d-%d/%s" - % (self.resumable_progress, chunk_end, size), # Must set the content-length header here because httplib can't # calculate the size when working with _StreamSlice. "Content-Length": str(chunk_end - self.resumable_progress + 1), } + # An empty file results in chunk_end = -1 and size = 0 + # sending "bytes 0--1/0" results in an invalid request + # Only add header "Content-Range" if chunk_end != -1 + if chunk_end != -1: + headers["Content-Range"] = "bytes %d-%d/%s" % (self.resumable_progress, chunk_end, size) + for retry_num in range(num_retries + 1): if retry_num > 0: self._sleep(self._rand() * 2 ** retry_num) diff --git a/tests/test_http.py b/tests/test_http.py index 2d74a7e4368..c7d9cc35df7 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -443,6 +443,33 @@ def test_media_io_base_next_chunk_no_retry_403_not_configured(self): request._sleep.assert_not_called() + def test_media_io_base_empty_file(self): + fd = BytesIO() + upload = MediaIoBaseUpload( + fd=fd, mimetype="image/png", chunksize=500, resumable=True + ) + + http = HttpMockSequence( + [ + ({"status": "200", "location": "https://www.googleapis.com/someapi/v1/upload?foo=bar"}, "{}"), + ({"status": "200", "location": "https://www.googleapis.com/someapi/v1/upload?foo=bar"}, "{}") + ] + ) + + model = JsonModel() + uri = u"https://www.googleapis.com/someapi/v1/upload/?foo=bar" + method = u"POST" + request = HttpRequest( + http, model.response, uri, method=method, headers={}, resumable=upload + ) + + request.execute() + + # Check that "Content-Range" header is not set in the PUT request + self.assertTrue("Content-Range" not in http.request_sequence[-1][-1]) + self.assertEqual("0", http.request_sequence[-1][-1]["Content-Length"]) + + class TestMediaIoBaseDownload(unittest.TestCase): def setUp(self): http = HttpMock(datafile("zoo.json"), {"status": "200"})