async def _validate_checksum(self, response): """Check the computed checksum, if any, against the response headers. Args: response (object): The HTTP response object. Raises: ~google.resumable_media.common.DataCorruption: If the checksum computed locally and the checksum reported by the remote host do not match. """ if self._checksum_type is None: return metadata_key = sync_helpers._get_metadata_key(self._checksum_type) metadata = await response.json() remote_checksum = metadata.get(metadata_key) if remote_checksum is None: raise common.InvalidResponse( response, _UPLOAD_METADATA_NO_APPROPRIATE_CHECKSUM_MESSAGE.format( metadata_key), self._get_headers(response), ) local_checksum = sync_helpers.prepare_checksum_digest( self._checksum_object.digest()) if local_checksum != remote_checksum: raise common.DataCorruption( response, _UPLOAD_CHECKSUM_MISMATCH_MESSAGE.format( self._checksum_type.upper(), local_checksum, remote_checksum), )
async def test__validate_checksum_mismatch(self, checksum): data = b"All of the data goes in a stream." upload = self._upload_in_flight(data, checksum=checksum) _fix_up_virtual(upload) # Go ahead and process the entire data in one go for this test. start_byte, payload, _ = _upload.get_next_chunk( upload._stream, len(data), len(data)) upload._update_checksum(start_byte, payload) assert upload._bytes_checksummed == len(data) metadata = { "md5Hash": "ZZZZZZZZZZZZZZZZZZZZZZ==", "crc32c": "ZZZZZZ==", } # This is only used by _validate_checksum for fetching headers and # logging, so it doesn't need to be fleshed out with a response body. response = _make_response(headers=metadata) upload._finished = True assert upload._checksum_object is not None # Test passes if it does not raise an error (no assert needed) with pytest.raises(common.DataCorruption) as exc_info: await upload._validate_checksum(response) error = exc_info.value assert error.response is response message = error.args[0] correct_checksums = { "crc32c": u"Qg8thA==", "md5": u"GRvfKbqr5klAOwLkxgIf8w==" } metadata_key = sync_helpers._get_metadata_key(checksum) assert message == _upload._UPLOAD_CHECKSUM_MISMATCH_MESSAGE.format( checksum.upper(), correct_checksums[checksum], metadata[metadata_key])
async def test__validate_checksum_header_no_match(self, checksum): data = b"All of the data goes in a stream." upload = self._upload_in_flight(data, checksum=checksum) _fix_up_virtual(upload) # Go ahead and process the entire data in one go for this test. start_byte, payload, _ = _upload.get_next_chunk( upload._stream, len(data), len(data)) upload._update_checksum(start_byte, payload) assert upload._bytes_checksummed == len(data) # For this test, each checksum option will be provided with a valid but # mismatching remote checksum type. if checksum == "crc32c": metadata = {"md5Hash": "GRvfKbqr5klAOwLkxgIf8w=="} else: metadata = {"crc32c": "Qg8thA=="} # This is only used by _validate_checksum for fetching headers and # logging, so it doesn't need to be fleshed out with a response body. response = _make_response(headers=metadata) upload._finished = True assert upload._checksum_object is not None with pytest.raises(common.InvalidResponse) as exc_info: await upload._validate_checksum(response) error = exc_info.value assert error.response is response message = error.args[0] metadata_key = sync_helpers._get_metadata_key(checksum) assert ( message == _upload._UPLOAD_METADATA_NO_APPROPRIATE_CHECKSUM_MESSAGE .format(metadata_key))
def _prepare_request(self, data, metadata, content_type): """Prepare the contents of an HTTP request. This is everything that must be done before a request that doesn't require network I/O (or other I/O). This is based on the `sans-I/O`_ philosophy. .. note: This method will be used only once, so ``headers`` will be mutated by having a new key added to it. Args: data (bytes): The resource content to be uploaded. metadata (Mapping[str, str]): The resource metadata, such as an ACL list. content_type (str): The content type of the resource, e.g. a JPEG image has content type ``image/jpeg``. Returns: Tuple[str, str, bytes, Mapping[str, str]]: The quadruple * HTTP verb for the request (always POST) * the URL for the request * the body of the request * headers for the request Raises: ValueError: If the current upload has already finished. TypeError: If ``data`` isn't bytes. .. _sans-I/O: https://sans-io.readthedocs.io/ """ if self.finished: raise ValueError("An upload can only be used once.") if not isinstance(data, bytes): raise TypeError("`data` must be bytes, received", type(data)) checksum_object = sync_helpers._get_checksum_object( self._checksum_type) if checksum_object is not None: checksum_object.update(data) actual_checksum = sync_helpers.prepare_checksum_digest( checksum_object.digest()) metadata_key = sync_helpers._get_metadata_key(self._checksum_type) metadata[metadata_key] = actual_checksum content, multipart_boundary = construct_multipart_request( data, metadata, content_type) multipart_content_type = _RELATED_HEADER + multipart_boundary + b'"' self._headers[_CONTENT_TYPE_HEADER] = multipart_content_type return _POST, self.upload_url, content, self._headers