diff --git a/gcloud/_helpers.py b/gcloud/_helpers.py index 5853785adfaf..475731221d4f 100644 --- a/gcloud/_helpers.py +++ b/gcloud/_helpers.py @@ -394,6 +394,30 @@ def _to_bytes(value, encoding='ascii'): raise TypeError('%r could not be converted to bytes' % (value,)) +def _from_bytes(value): + """Converts bytes to a string value, if necessary. + + Args: + value: The string/bytes value to be converted. + + Returns: + The original value converted to unicode (if bytes) or as passed in + if it started out as unicode. + + Raises: + ValueError if the value could not be converted to unicode. + + :type value: bytes + :param value: bytes value to attempt string conversion on. + """ + result = (value.decode('utf-8') + if isinstance(value, six.binary_type) else value) + if isinstance(result, six.text_type): + return result + else: + raise ValueError('%r could not be converted to unicode' % (value,)) + + def _pb_timestamp_to_datetime(timestamp): """Convert a Timestamp protobuf to a datetime object. diff --git a/gcloud/storage/blob.py b/gcloud/storage/blob.py index a0ce672b53a3..1a5b847f1d8c 100644 --- a/gcloud/storage/blob.py +++ b/gcloud/storage/blob.py @@ -14,7 +14,7 @@ """Create / interact with Google Cloud Storage blobs.""" -import base64 +from base64 import b64encode import copy import hashlib from io import BytesIO @@ -28,6 +28,8 @@ from six.moves.urllib.parse import quote from gcloud._helpers import _rfc3339_to_datetime +from gcloud._helpers import _to_bytes +from gcloud._helpers import _from_bytes from gcloud.credentials import generate_signed_url from gcloud.exceptions import NotFound from gcloud.exceptions import make_exception @@ -111,23 +113,6 @@ def path_helper(bucket_path, blob_name): """ return bucket_path + '/o/' + quote(blob_name, safe='') - @staticmethod - def _get_customer_encryption_headers(key): - """Builds customer encyrption key headers - - :type key: str - :param key: 32 byte key to build request key and hash. - """ - headers = {} - key_hash = base64.encodestring(hashlib.sha256(key.encode('utf-8')) - .digest()).rstrip() - encoded_key = base64.encodestring(bytes(key.encode('utf-8'))).rstrip() - headers['X-Goog-Encryption-Algorithm'] = 'AES256' - headers['X-Goog-Encryption-Key'] = encoded_key.decode('utf-8') - headers['X-Goog-Encryption-Key-Sha256'] = key_hash.decode('utf-8') - - return headers - @property def acl(self): """Create our ACL on demand.""" @@ -343,7 +328,7 @@ def download_to_file(self, file_obj, key=None, client=None): headers = {} if key: - headers.update(self._get_customer_encryption_headers(key)) + set_customer_encryption_headers(key, headers) request = Request(download_url, 'GET', headers) @@ -417,7 +402,7 @@ def _check_response_error(request, http_response): raise make_exception(faux_response, http_response.content, error_info=request.url) - # pylint: disable=too-many-arguments,too-many-locals + # pylint: disable=too-many-locals def upload_from_file(self, file_obj, rewind=False, size=None, key=None, content_type=None, num_retries=6, client=None): """Upload the contents of this blob from a file-like object. @@ -498,7 +483,7 @@ def upload_from_file(self, file_obj, rewind=False, size=None, key=None, } if key: - headers.update(self._get_customer_encryption_headers(key)) + set_customer_encryption_headers(key, headers) upload = Upload(file_obj, content_type, total_bytes, auto_transfer=False) @@ -923,3 +908,21 @@ def __init__(self, bucket_name, object_name): self.query_params = {'name': object_name} self._bucket_name = bucket_name self._relative_path = '' + + +def set_customer_encryption_headers(key, headers): + """Builds customer encyrption key headers + + :type key: str + :param key: 32 byte key to build request key and hash. + + :type headers: dict + :param headers: dict of HTTP headers being sent in request. + """ + key = _to_bytes(key) + sha256_key = hashlib.sha256(key).digest() + key_hash = b64encode(sha256_key).rstrip() + encoded_key = b64encode(key).rstrip() + headers['X-Goog-Encryption-Algorithm'] = 'AES256' + headers['X-Goog-Encryption-Key'] = _from_bytes(encoded_key) + headers['X-Goog-Encryption-Key-Sha256'] = _from_bytes(key_hash)