1# -------------------------------------------------------------------------
2# Copyright (c) Microsoft Corporation. All rights reserved.
3# Licensed under the MIT License. See License.txt in the project root for
4# license information.
5# --------------------------------------------------------------------------
6
7from typing import (  # pylint: disable=unused-import
8    Union, Optional, Any, Iterable, Dict, List, Type, Tuple,
9    TYPE_CHECKING
10)
11import logging
12
13from azure.core.pipeline.policies import ContentDecodePolicy
14from azure.core.exceptions import (
15    HttpResponseError,
16    ResourceNotFoundError,
17    ResourceModifiedError,
18    ResourceExistsError,
19    ClientAuthenticationError,
20    DecodeError)
21
22from .parser import _to_utc_datetime
23from .models import StorageErrorCode, UserDelegationKey, get_enum_value
24
25
26if TYPE_CHECKING:
27    from datetime import datetime
28    from azure.core.exceptions import AzureError
29
30
31_LOGGER = logging.getLogger(__name__)
32
33
34class PartialBatchErrorException(HttpResponseError):
35    """There is a partial failure in batch operations.
36
37    :param str message: The message of the exception.
38    :param response: Server response to be deserialized.
39    :param list parts: A list of the parts in multipart response.
40    """
41
42    def __init__(self, message, response, parts):
43        self.parts = parts
44        super(PartialBatchErrorException, self).__init__(message=message, response=response)
45
46
47def parse_length_from_content_range(content_range):
48    '''
49    Parses the blob length from the content range header: bytes 1-3/65537
50    '''
51    if content_range is None:
52        return None
53
54    # First, split in space and take the second half: '1-3/65537'
55    # Next, split on slash and take the second half: '65537'
56    # Finally, convert to an int: 65537
57    return int(content_range.split(' ', 1)[1].split('/', 1)[1])
58
59
60def normalize_headers(headers):
61    normalized = {}
62    for key, value in headers.items():
63        if key.startswith('x-ms-'):
64            key = key[5:]
65        normalized[key.lower().replace('-', '_')] = get_enum_value(value)
66    return normalized
67
68
69def deserialize_metadata(response, obj, headers):  # pylint: disable=unused-argument
70    raw_metadata = {k: v for k, v in response.http_response.headers.items() if k.startswith("x-ms-meta-")}
71    return {k[10:]: v for k, v in raw_metadata.items()}
72
73
74def return_response_headers(response, deserialized, response_headers):  # pylint: disable=unused-argument
75    return normalize_headers(response_headers)
76
77
78def return_headers_and_deserialized(response, deserialized, response_headers):  # pylint: disable=unused-argument
79    return normalize_headers(response_headers), deserialized
80
81
82def return_context_and_deserialized(response, deserialized, response_headers):  # pylint: disable=unused-argument
83    return response.http_response.location_mode, deserialized
84
85
86def process_storage_error(storage_error):
87    # If storage_error is one of the two then it has already been processed and serialized to the specific exception.
88    if isinstance(storage_error, (PartialBatchErrorException, ClientAuthenticationError)):
89        raise storage_error
90    raise_error = HttpResponseError
91    error_code = storage_error.response.headers.get('x-ms-error-code')
92    error_message = storage_error.message
93    additional_data = {}
94    try:
95        error_body = ContentDecodePolicy.deserialize_from_http_generics(storage_error.response)
96        if error_body:
97            for info in error_body.iter():
98                if info.tag.lower() == 'code':
99                    error_code = info.text
100                elif info.tag.lower() == 'message':
101                    error_message = info.text
102                else:
103                    additional_data[info.tag] = info.text
104    except DecodeError:
105        pass
106
107    try:
108        if error_code:
109            error_code = StorageErrorCode(error_code)
110            if error_code in [StorageErrorCode.condition_not_met,
111                              StorageErrorCode.blob_overwritten]:
112                raise_error = ResourceModifiedError
113            if error_code in [StorageErrorCode.invalid_authentication_info,
114                              StorageErrorCode.authentication_failed]:
115                raise_error = ClientAuthenticationError
116            if error_code in [StorageErrorCode.resource_not_found,
117                              StorageErrorCode.cannot_verify_copy_source,
118                              StorageErrorCode.blob_not_found,
119                              StorageErrorCode.queue_not_found,
120                              StorageErrorCode.container_not_found,
121                              StorageErrorCode.parent_not_found,
122                              StorageErrorCode.share_not_found]:
123                raise_error = ResourceNotFoundError
124            if error_code in [StorageErrorCode.account_already_exists,
125                              StorageErrorCode.account_being_created,
126                              StorageErrorCode.resource_already_exists,
127                              StorageErrorCode.resource_type_mismatch,
128                              StorageErrorCode.blob_already_exists,
129                              StorageErrorCode.queue_already_exists,
130                              StorageErrorCode.container_already_exists,
131                              StorageErrorCode.container_being_deleted,
132                              StorageErrorCode.queue_being_deleted,
133                              StorageErrorCode.share_already_exists,
134                              StorageErrorCode.share_being_deleted]:
135                raise_error = ResourceExistsError
136    except ValueError:
137        # Got an unknown error code
138        pass
139
140    try:
141        error_message += "\nErrorCode:{}".format(error_code.value)
142    except AttributeError:
143        error_message += "\nErrorCode:{}".format(error_code)
144    for name, info in additional_data.items():
145        error_message += "\n{}:{}".format(name, info)
146
147    error = raise_error(message=error_message, response=storage_error.response)
148    error.error_code = error_code
149    error.additional_info = additional_data
150    error.raise_with_traceback()
151
152
153def parse_to_internal_user_delegation_key(service_user_delegation_key):
154    internal_user_delegation_key = UserDelegationKey()
155    internal_user_delegation_key.signed_oid = service_user_delegation_key.signed_oid
156    internal_user_delegation_key.signed_tid = service_user_delegation_key.signed_tid
157    internal_user_delegation_key.signed_start = _to_utc_datetime(service_user_delegation_key.signed_start)
158    internal_user_delegation_key.signed_expiry = _to_utc_datetime(service_user_delegation_key.signed_expiry)
159    internal_user_delegation_key.signed_service = service_user_delegation_key.signed_service
160    internal_user_delegation_key.signed_version = service_user_delegation_key.signed_version
161    internal_user_delegation_key.value = service_user_delegation_key.value
162    return internal_user_delegation_key
163