Skip to content

Commit f4a5b09

Browse files
committed
[Storage][102] CRC64 content validation - part 6 - file-share (#46262)
1 parent a5c2e39 commit f4a5b09

31 files changed

Lines changed: 834 additions & 230 deletions

sdk/storage/azure-storage-blob/azure/storage/blob/_blob_client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
from ._quick_query_helper import BlobQueryReader
6666
from ._shared.base_client import parse_connection_str, StorageAccountHostsMixin, TransportWrapper
6767
from ._shared.response_handlers import process_storage_error, return_response_headers
68-
from ._shared.validation import ChecksumAlgorithm, parse_validation_option
68+
from ._shared.validation import is_crc64_validation, parse_validation_option
6969
from ._serialize import (
7070
get_access_conditions,
7171
get_api_version,
@@ -614,7 +614,7 @@ def upload_blob(
614614
if kwargs.get('cpk') and self.scheme.lower() != 'https':
615615
raise ValueError("Customer provided encryption key must be used over HTTPS.")
616616
validate_content = parse_validation_option(kwargs.pop('validate_content', None))
617-
if validate_content == ChecksumAlgorithm.CRC64 and self.key_encryption_key:
617+
if is_crc64_validation(validate_content) and self.key_encryption_key:
618618
raise ValueError("Using encryption and content validation together is not currently supported.")
619619
options = _upload_blob_options(
620620
data=data,
@@ -763,7 +763,7 @@ def download_blob(
763763
if kwargs.get('cpk') and self.scheme.lower() != 'https':
764764
raise ValueError("Customer provided encryption key must be used over HTTPS.")
765765
validate_content = parse_validation_option(kwargs.pop('validate_content', None))
766-
if validate_content == ChecksumAlgorithm.CRC64 and self.key_encryption_key:
766+
if is_crc64_validation(validate_content) and self.key_encryption_key:
767767
raise ValueError("Using encryption and content validation together is not currently supported.")
768768
options = _download_blob_options(
769769
blob_name=self.blob_name,

sdk/storage/azure-storage-blob/azure/storage/blob/_blob_client_helpers.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from io import BytesIO
99
from typing import (
1010
Any, AnyStr, AsyncGenerator, AsyncIterable, cast,
11-
Dict, IO, Iterable, List, Literal, Optional, Tuple, Union,
11+
Dict, IO, Iterable, List, Optional, Tuple, Union,
1212
TYPE_CHECKING
1313
)
1414
from urllib.parse import quote, unquote, urlparse
@@ -58,7 +58,7 @@
5858
from ._shared.response_handlers import return_headers_and_deserialized, return_response_headers
5959
from ._shared.uploads import IterStreamer
6060
from ._shared.uploads_async import AsyncIterStreamer
61-
from ._shared.validation import parse_validation_option
61+
from ._shared.validation import CV_TYPE_PARSED, parse_validation_option
6262
from ._upload_helpers import _any_conditions
6363

6464
if TYPE_CHECKING:
@@ -111,7 +111,7 @@ def _upload_blob_options( # pylint:disable=too-many-statements
111111
length: Optional[int],
112112
metadata: Optional[Dict[str, str]],
113113
encryption_options: Dict[str, Any],
114-
validate_content: Optional[Union[bool, Literal['auto', 'crc64', 'md5']]],
114+
validate_content: CV_TYPE_PARSED,
115115
config: "StorageConfiguration",
116116
sdk_moniker: str,
117117
client: "AzureBlobStorage",
@@ -259,7 +259,7 @@ def _download_blob_options(
259259
length: Optional[int],
260260
encoding: Optional[str],
261261
encryption_options: Dict[str, Any],
262-
validate_content: Optional[Union[bool, Literal['auto', 'crc64', 'md5']]],
262+
validate_content: CV_TYPE_PARSED,
263263
config: "StorageConfiguration",
264264
sdk_moniker: str,
265265
client: "AzureBlobStorage",

sdk/storage/azure-storage-blob/azure/storage/blob/_download.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from io import BytesIO, StringIO
1212
from typing import (
1313
Any, Callable, cast, Dict, Generator,
14-
Generic, IO, Iterator, List, Literal, Optional,
14+
Generic, IO, Iterator, List, Optional,
1515
overload, Tuple, TypeVar, Union, TYPE_CHECKING
1616
)
1717

@@ -21,7 +21,7 @@
2121
from ._shared.request_handlers import validate_and_format_range_headers
2222
from ._shared.response_handlers import parse_length_from_content_range, process_storage_error
2323
from ._shared.constants import DEFAULT_MAX_CONCURRENCY
24-
from ._shared.validation import is_md5_validation
24+
from ._shared.validation import is_md5_validation, CV_TYPE_PARSED
2525
from ._deserialize import deserialize_blob_properties, get_page_ranges_result
2626
from ._encryption import (
2727
adjust_blob_size_for_encryption,
@@ -92,7 +92,7 @@ def __init__(
9292
current_progress: int,
9393
start_range: int,
9494
end_range: int,
95-
validate_content: Optional[Union[bool, Literal['crc64', 'md5']]],
95+
validate_content: CV_TYPE_PARSED,
9696
encryption_options: Dict[str, Any],
9797
encryption_data: Optional["_EncryptionData"] = None,
9898
stream: Any = None,
@@ -330,7 +330,7 @@ def __init__(
330330
config: "StorageConfiguration" = None, # type: ignore [assignment]
331331
start_range: Optional[int] = None,
332332
end_range: Optional[int] = None,
333-
validate_content: Optional[Union[bool, Literal['crc64', 'md5']]] = None,
333+
validate_content: CV_TYPE_PARSED = None,
334334
encryption_options: Dict[str, Any] = None, # type: ignore [assignment]
335335
max_concurrency: Optional[int] = None,
336336
name: str = None, # type: ignore [assignment]

sdk/storage/azure-storage-blob/azure/storage/blob/_shared/policies.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@
55
# --------------------------------------------------------------------------
66

77
import base64
8-
import hashlib
98
import logging
109
import random
1110
import re
1211
import uuid
13-
from io import SEEK_SET, UnsupportedOperation
12+
from io import BytesIO, SEEK_SET, UnsupportedOperation
1413
from time import time
1514
from typing import Any, Dict, Optional, TYPE_CHECKING, Union
1615
from urllib.parse import (
@@ -43,8 +42,8 @@
4342
CV_TYPE_ERROR_MSG,
4443
calculate_content_md5,
4544
calculate_crc64_bytes,
45+
is_crc64_validation,
4646
is_md5_validation,
47-
ChecksumAlgorithm,
4847
)
4948

5049
if TYPE_CHECKING:
@@ -428,7 +427,7 @@ def _prepare_content_validation(request: "PipelineRequest") -> None:
428427

429428
# Download
430429
if request.http_request.method == "GET":
431-
if validate_content == ChecksumAlgorithm.CRC64:
430+
if is_crc64_validation(validate_content):
432431
request.http_request.headers[SM_HEADER] = SM_HEADER_V1_CRC64
433432

434433
# Upload
@@ -441,7 +440,11 @@ def _prepare_content_validation(request: "PipelineRequest") -> None:
441440
request.http_request.headers[MD5_HEADER] = computed_md5
442441
request.context["validate_content_md5"] = computed_md5
443442

444-
elif validate_content == ChecksumAlgorithm.CRC64:
443+
elif is_crc64_validation(validate_content):
444+
# For crc64-sm, force structured message even for bytes
445+
if validate_content == "crc64-sm" and isinstance(data, bytes):
446+
data = BytesIO(data)
447+
445448
if isinstance(data, bytes):
446449
request.http_request.headers[CRC64_HEADER] = encode_base64(
447450
calculate_crc64_bytes(data)
@@ -495,7 +498,7 @@ def _validate_content_response(
495498
response=response.http_response,
496499
)
497500

498-
elif validate_content == ChecksumAlgorithm.CRC64:
501+
elif is_crc64_validation(validate_content):
499502
# For upload and download verify structured message header present in response if provided in request.
500503
sm_request = request.http_request.headers.get(SM_HEADER)
501504
sm_response = response.http_response.headers.get(SM_HEADER)

sdk/storage/azure-storage-blob/azure/storage/blob/_shared/validation.py

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,16 @@
66
# pylint: disable=c-extension-no-member
77

88
import hashlib
9-
from enum import Enum
109
from io import SEEK_SET
1110
from typing import IO, Literal, Optional, Union, cast
1211

13-
from azure.core import CaseInsensitiveEnumMeta
14-
1512
CRC64_LENGTH = 8
1613
CV_TYPE_ERROR_MSG = "Data should be bytes or seekable IO[bytes] for content validation."
1714

15+
_VALID_CV_OPTIONS = ("auto", "crc64", "crc64-sm", "md5")
1816

19-
class ChecksumAlgorithm(str, Enum, metaclass=CaseInsensitiveEnumMeta):
20-
AUTO = "auto"
21-
MD5 = "md5"
22-
CRC64 = "crc64"
23-
24-
@classmethod
25-
def list(cls):
26-
return list(map(lambda c: c.value, cls))
17+
CV_TYPE = Optional[Union[bool, Literal["auto", "crc64", "md5"]]]
18+
CV_TYPE_PARSED = Optional[Union[bool, Literal["crc64", "crc64-sm", "md5"]]]
2719

2820

2921
def _verify_extensions(module: str) -> None:
@@ -37,36 +29,51 @@ def _verify_extensions(module: str) -> None:
3729

3830

3931
def parse_validation_option(
40-
validate_content: Optional[Union[bool, Literal["auto", "crc64", "md5"]]],
41-
) -> Optional[Union[bool, Literal["auto", "crc64", "md5"]]]:
32+
validate_content: CV_TYPE,
33+
*,
34+
force_structured_message: bool = False,
35+
) -> CV_TYPE_PARSED:
4236
if validate_content is None:
4337
return None
4438

4539
# Legacy support for bool
4640
if isinstance(validate_content, bool):
4741
return validate_content
4842

49-
if validate_content not in (ChecksumAlgorithm.list()):
43+
parsed = validate_content.lower()
44+
if parsed not in _VALID_CV_OPTIONS:
5045
raise ValueError("Invalid value for `validate_content` specified.")
5146

5247
# Resolve auto
53-
if validate_content == ChecksumAlgorithm.AUTO:
54-
validate_content = ChecksumAlgorithm.CRC64.value
48+
if parsed == "auto":
49+
parsed = "crc64"
5550

56-
if validate_content == ChecksumAlgorithm.CRC64:
51+
if parsed == "crc64":
5752
_verify_extensions("crc64")
53+
if force_structured_message:
54+
parsed = "crc64-sm"
5855

59-
return validate_content
56+
return cast(CV_TYPE_PARSED, parsed)
6057

6158

6259
def is_md5_validation(
63-
validate_content: Optional[Union[bool, Literal["md5", "crc64"]]],
60+
validate_content: CV_TYPE_PARSED,
6461
) -> bool:
6562
if validate_content is None:
6663
return False
6764
if isinstance(validate_content, bool):
6865
return validate_content
69-
return validate_content == ChecksumAlgorithm.MD5
66+
return validate_content == "md5"
67+
68+
69+
def is_crc64_validation(
70+
validate_content: CV_TYPE_PARSED,
71+
) -> bool:
72+
if validate_content is None:
73+
return False
74+
if isinstance(validate_content, bool):
75+
return False
76+
return validate_content in ("crc64", "crc64-sm")
7077

7178

7279
def calculate_content_md5(data: Union[bytes, IO[bytes]]) -> bytes:

sdk/storage/azure-storage-blob/azure/storage/blob/_upload_helpers.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# --------------------------------------------------------------------------
66

77
from io import SEEK_SET, UnsupportedOperation
8-
from typing import Any, cast, Dict, IO, Literal, Optional, TypeVar, Union, TYPE_CHECKING
8+
from typing import Any, cast, Dict, IO, Optional, TypeVar, TYPE_CHECKING
99

1010
from azure.core.exceptions import ResourceExistsError, ResourceModifiedError, HttpResponseError
1111

@@ -32,6 +32,7 @@
3232
upload_data_chunks,
3333
upload_substream_blocks
3434
)
35+
from ._shared.validation import CV_TYPE_PARSED
3536

3637
if TYPE_CHECKING:
3738
from ._generated.operations import AppendBlobOperations, BlockBlobOperations, PageBlobOperations
@@ -71,7 +72,7 @@ def upload_block_blob( # pylint: disable=too-many-locals, too-many-statements
7172
encryption_options: Dict[str, Any],
7273
blob_settings: "StorageConfiguration",
7374
headers: Dict[str, Any],
74-
validate_content: Optional[Union[bool, Literal['crc64', 'md5']]],
75+
validate_content: CV_TYPE_PARSED,
7576
max_concurrency: Optional[int],
7677
length: Optional[int] = None,
7778
**kwargs: Any
@@ -213,7 +214,7 @@ def upload_page_blob(
213214
headers: Dict[str, Any],
214215
stream: IO,
215216
length: Optional[int] = None,
216-
validate_content: Optional[Union[bool, Literal['crc64', 'md5']]] = None,
217+
validate_content: CV_TYPE_PARSED = None,
217218
max_concurrency: Optional[int] = None,
218219
**kwargs: Any
219220
) -> Dict[str, Any]:
@@ -291,7 +292,7 @@ def upload_append_blob( # pylint: disable=unused-argument
291292
headers: Dict[str, Any],
292293
stream: IO,
293294
length: Optional[int] = None,
294-
validate_content: Optional[Union[bool, Literal['crc64', 'md5']]] = None,
295+
validate_content: CV_TYPE_PARSED = None,
295296
max_concurrency: Optional[int] = None,
296297
**kwargs: Any
297298
) -> Dict[str, Any]:

sdk/storage/azure-storage-blob/azure/storage/blob/aio/_blob_client_async.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
from .._shared.base_client_async import AsyncStorageAccountHostsMixin, AsyncTransportWrapper, parse_connection_str
7878
from .._shared.policies_async import ExponentialRetry
7979
from .._shared.response_handlers import process_storage_error, return_response_headers
80-
from .._shared.validation import ChecksumAlgorithm, parse_validation_option
80+
from .._shared.validation import is_crc64_validation, parse_validation_option
8181

8282
if TYPE_CHECKING:
8383
from azure.core import MatchConditions
@@ -627,7 +627,7 @@ async def upload_blob(
627627
if kwargs.get('cpk') and self.scheme.lower() != 'https':
628628
raise ValueError("Customer provided encryption key must be used over HTTPS.")
629629
validate_content = parse_validation_option(kwargs.pop('validate_content', None))
630-
if validate_content == ChecksumAlgorithm.CRC64 and self.key_encryption_key:
630+
if is_crc64_validation(validate_content) and self.key_encryption_key:
631631
raise ValueError("Using encryption and content validation together is not currently supported.")
632632
options = _upload_blob_options(
633633
data=data,
@@ -776,7 +776,7 @@ async def download_blob(
776776
if kwargs.get('cpk') and self.scheme.lower() != 'https':
777777
raise ValueError("Customer provided encryption key must be used over HTTPS.")
778778
validate_content = parse_validation_option(kwargs.pop('validate_content', None))
779-
if validate_content == ChecksumAlgorithm.CRC64 and self.key_encryption_key:
779+
if is_crc64_validation(validate_content) and self.key_encryption_key:
780780
raise ValueError("Using encryption and content validation together is not currently supported.")
781781
options = _download_blob_options(
782782
blob_name=self.blob_name,

sdk/storage/azure-storage-blob/azure/storage/blob/aio/_download_async.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from typing import (
1616
Any, AsyncIterator, Awaitable,
1717
Generator, Callable, cast, Dict,
18-
Generic, IO, Literal, Optional, overload,
18+
Generic, IO, Optional, overload,
1919
Tuple, TypeVar, Union, TYPE_CHECKING
2020
)
2121

@@ -24,7 +24,7 @@
2424
from .._shared.request_handlers import validate_and_format_range_headers
2525
from .._shared.response_handlers import parse_length_from_content_range, process_storage_error
2626
from .._shared.constants import DEFAULT_MAX_CONCURRENCY
27-
from .._shared.validation import is_md5_validation
27+
from .._shared.validation import is_md5_validation, CV_TYPE_PARSED
2828
from .._deserialize import deserialize_blob_properties, get_page_ranges_result
2929
from .._download import process_range_and_offset, _ChunkDownloader
3030
from .._encryption import (
@@ -239,7 +239,7 @@ def __init__(
239239
config: "StorageConfiguration" = None, # type: ignore [assignment]
240240
start_range: Optional[int] = None,
241241
end_range: Optional[int] = None,
242-
validate_content: Optional[Union[bool, Literal['crc64', 'md5']]] = None,
242+
validate_content: CV_TYPE_PARSED = None,
243243
encryption_options: Dict[str, Any] = None, # type: ignore [assignment]
244244
max_concurrency: Optional[int] = None,
245245
name: str = None, # type: ignore [assignment]

sdk/storage/azure-storage-blob/azure/storage/blob/aio/_upload_helpers.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import inspect
88
from io import SEEK_SET, UnsupportedOperation
9-
from typing import Any, cast, Dict, IO, Literal, Optional, TypeVar, Union, TYPE_CHECKING
9+
from typing import Any, cast, Dict, IO, Optional, TypeVar, TYPE_CHECKING
1010

1111
from azure.core.exceptions import HttpResponseError, ResourceModifiedError
1212

@@ -32,6 +32,7 @@
3232
upload_data_chunks,
3333
upload_substream_blocks
3434
)
35+
from .._shared.validation import CV_TYPE_PARSED
3536
from .._upload_helpers import _any_conditions, _convert_mod_error
3637

3738
if TYPE_CHECKING:
@@ -47,7 +48,7 @@ async def upload_block_blob( # pylint: disable=too-many-locals, too-many-statem
4748
encryption_options: Dict[str, Any],
4849
blob_settings: "StorageConfiguration",
4950
headers: Dict[str, Any],
50-
validate_content: Optional[Union[bool, Literal['crc64', 'md5']]],
51+
validate_content: CV_TYPE_PARSED,
5152
max_concurrency: Optional[int],
5253
length: Optional[int] = None,
5354
**kwargs: Any
@@ -193,7 +194,7 @@ async def upload_page_blob(
193194
headers: Dict[str, Any],
194195
stream: IO,
195196
length: Optional[int] = None,
196-
validate_content: Optional[Union[bool, Literal['crc64', 'md5']]] = None,
197+
validate_content: CV_TYPE_PARSED = None,
197198
max_concurrency: Optional[int] = None,
198199
**kwargs: Any
199200
) -> Dict[str, Any]:
@@ -271,7 +272,7 @@ async def upload_append_blob( # pylint: disable=unused-argument
271272
headers: Dict[str, Any],
272273
stream: IO,
273274
length: Optional[int] = None,
274-
validate_content: Optional[Union[bool, Literal['crc64', 'md5']]] = None,
275+
validate_content: CV_TYPE_PARSED = None,
275276
max_concurrency: Optional[int] = None,
276277
**kwargs: Any
277278
) -> Dict[str, Any]:

0 commit comments

Comments
 (0)