diff --git a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/authentication.py b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/authentication.py index d04c1e4fb539..90ade822dab1 100644 --- a/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/authentication.py +++ b/sdk/storage/azure-storage-blob/azure/storage/blob/_shared/authentication.py @@ -6,6 +6,7 @@ import logging import sys +import locale try: from urllib.parse import urlparse, unquote @@ -30,7 +31,7 @@ logger = logging.getLogger(__name__) - +locale.setlocale(locale.LC_COLLATE, "") # wraps a given exception with the desired exception type @@ -92,10 +93,19 @@ def _get_canonicalized_resource(self, request): def _get_canonicalized_headers(request): string_to_sign = '' x_ms_headers = [] + x_ms_headers_dict = {} + x_ms_headers_strings = [] for name, value in request.http_request.headers.items(): if name.startswith('x-ms-'): x_ms_headers.append((name.lower(), value)) - x_ms_headers.sort() + for tup in x_ms_headers: + tup_string = ''.join(tup) + x_ms_headers_dict[tup_string] = tup + x_ms_headers_strings.append(tup_string) + + x_ms_headers_strings.sort(key=locale.strxfrm) + x_ms_headers = [x_ms_headers_dict[sorted_string] for sorted_string in x_ms_headers_strings] + for name, value in x_ms_headers: if value is not None: string_to_sign += ''.join([name, ':', value, '\n']) diff --git a/sdk/storage/azure-storage-blob/tests/recordings/test_blob_access_conditions.test_header_metadata_sort_in_upload_blob.yaml b/sdk/storage/azure-storage-blob/tests/recordings/test_blob_access_conditions.test_header_metadata_sort_in_upload_blob.yaml new file mode 100644 index 000000000000..68cdfebbe0f4 --- /dev/null +++ b/sdk/storage/azure-storage-blob/tests/recordings/test_blob_access_conditions.test_header_metadata_sort_in_upload_blob.yaml @@ -0,0 +1,146 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - azsdk-python-storage-blob/12.5.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-date: + - Sun, 13 Sep 2020 18:17:12 GMT + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainerbd791bbe?restype=container + response: + body: + string: '' + headers: + content-length: + - '0' + date: + - Sun, 13 Sep 2020 18:17:12 GMT + etag: + - '"0x8D858113CD5E782"' + last-modified: + - Sun, 13 Sep 2020 18:17:13 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-version: + - '2019-12-12' + status: + code: 201 + message: Created +- request: + body: hello world + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '11' + Content-Type: + - application/octet-stream + If-None-Match: + - '*' + User-Agent: + - azsdk-python-storage-blob/12.5.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-blob-type: + - BlockBlob + x-ms-date: + - Sun, 13 Sep 2020 18:17:13 GMT + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainerbd791bbe/blob1 + response: + body: + string: '' + headers: + content-length: + - '0' + content-md5: + - XrY7u+Ae7tCTyyK7j1rNww== + date: + - Sun, 13 Sep 2020 18:17:12 GMT + etag: + - '"0x8D858113CE74816"' + last-modified: + - Sun, 13 Sep 2020 18:17:13 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: + - vo7q9sPVKY0= + x-ms-request-server-encrypted: + - 'true' + x-ms-version: + - '2019-12-12' + status: + code: 201 + message: Created +- request: + body: hello world + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '11' + Content-Type: + - application/octet-stream + If-Modified-Since: + - Sun, 13 Sep 2020 18:02:13 GMT + User-Agent: + - azsdk-python-storage-blob/12.5.0 Python/3.8.5 (Windows-10-10.0.18362-SP0) + x-ms-blob-type: + - BlockBlob + x-ms-date: + - Sun, 13 Sep 2020 18:17:13 GMT + x-ms-meta-_a_: + - d + x-ms-meta-i0: + - a + x-ms-meta-i_: + - a + x-ms-version: + - '2019-12-12' + method: PUT + uri: https://storagename.blob.core.windows.net/utcontainerbd791bbe/blob1 + response: + body: + string: '' + headers: + content-length: + - '0' + content-md5: + - XrY7u+Ae7tCTyyK7j1rNww== + date: + - Sun, 13 Sep 2020 18:17:12 GMT + etag: + - '"0x8D858113CFBE550"' + last-modified: + - Sun, 13 Sep 2020 18:17:13 GMT + server: + - Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0 + x-ms-content-crc64: + - vo7q9sPVKY0= + x-ms-request-server-encrypted: + - 'true' + x-ms-version: + - '2019-12-12' + status: + code: 201 + message: Created +version: 1 diff --git a/sdk/storage/azure-storage-blob/tests/test_blob_access_conditions.py b/sdk/storage/azure-storage-blob/tests/test_blob_access_conditions.py index d711422decb5..7036ad7cc061 100644 --- a/sdk/storage/azure-storage-blob/tests/test_blob_access_conditions.py +++ b/sdk/storage/azure-storage-blob/tests/test_blob_access_conditions.py @@ -285,6 +285,22 @@ def test_delete_container_with_if_unmodified_fail(self, resource_group, location self.assertEqual(StorageErrorCode.condition_not_met, e.exception.error_code) + @GlobalStorageAccountPreparer() + def test_header_metadata_sort_in_upload_blob(self, resource_group, location, storage_account, storage_account_key): + bsc = BlobServiceClient(self.account_url(storage_account, "blob"), storage_account_key, connection_data_block_size=4 * 1024) + self._setup() + data = b'hello world' + container, blob = self._create_container_and_block_blob( + self.container_name, 'blob1', data, bsc) + test_datetime = (datetime.utcnow() - + timedelta(minutes=15)) + metadata = {'i0': 'a', + 'i_': 'a', + '_a_': 'd' + } + # Act + resp = blob.upload_blob(data, length=len(data), if_modified_since=test_datetime, metadata=metadata) + @GlobalStorageAccountPreparer() def test_put_blob_with_if_modified(self, resource_group, location, storage_account, storage_account_key): bsc = BlobServiceClient(self.account_url(storage_account, "blob"), storage_account_key, connection_data_block_size=4 * 1024)