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.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.location_mode, deserialized
84
85
86def process_storage_error(storage_error):
87    raise_error = HttpResponseError
88    error_code = storage_error.response.headers.get('x-ms-error-code')
89    error_message = storage_error.message
90    additional_data = {}
91    try:
92        error_body = ContentDecodePolicy.deserialize_from_http_generics(storage_error.response)
93        if error_body:
94            for info in error_body.iter():
95                if info.tag.lower() == 'code':
96                    error_code = info.text
97                elif info.tag.lower() == 'message':
98                    error_message = info.text
99                else:
100                    additional_data[info.tag] = info.text
101    except DecodeError:
102        pass
103
104    try:
105        if error_code:
106            error_code = StorageErrorCode(error_code)
107            if error_code in [StorageErrorCode.condition_not_met,
108                              StorageErrorCode.blob_overwritten]:
109                raise_error = ResourceModifiedError
110            if error_code in [StorageErrorCode.invalid_authentication_info,
111                              StorageErrorCode.authentication_failed]:
112                raise_error = ClientAuthenticationError
113            if error_code in [StorageErrorCode.resource_not_found,
114                              StorageErrorCode.cannot_verify_copy_source,
115                              StorageErrorCode.blob_not_found,
116                              StorageErrorCode.queue_not_found,
117                              StorageErrorCode.container_not_found,
118                              StorageErrorCode.parent_not_found,
119                              StorageErrorCode.share_not_found]:
120                raise_error = ResourceNotFoundError
121            if error_code in [StorageErrorCode.account_already_exists,
122                              StorageErrorCode.account_being_created,
123                              StorageErrorCode.resource_already_exists,
124                              StorageErrorCode.resource_type_mismatch,
125                              StorageErrorCode.blob_already_exists,
126                              StorageErrorCode.queue_already_exists,
127                              StorageErrorCode.container_already_exists,
128                              StorageErrorCode.container_being_deleted,
129                              StorageErrorCode.queue_being_deleted,
130                              StorageErrorCode.share_already_exists,
131                              StorageErrorCode.share_being_deleted]:
132                raise_error = ResourceExistsError
133    except ValueError:
134        # Got an unknown error code
135        pass
136
137    try:
138        error_message += "\nErrorCode:{}".format(error_code.value)
139    except AttributeError:
140        error_message += "\nErrorCode:{}".format(error_code)
141    for name, info in additional_data.items():
142        error_message += "\n{}:{}".format(name, info)
143
144    error = raise_error(message=error_message, response=storage_error.response)
145    error.error_code = error_code
146    error.additional_info = additional_data
147    raise error
148
149
150def parse_to_internal_user_delegation_key(service_user_delegation_key):
151    internal_user_delegation_key = UserDelegationKey()
152    internal_user_delegation_key.signed_oid = service_user_delegation_key.signed_oid
153    internal_user_delegation_key.signed_tid = service_user_delegation_key.signed_tid
154    internal_user_delegation_key.signed_start = _to_utc_datetime(service_user_delegation_key.signed_start)
155    internal_user_delegation_key.signed_expiry = _to_utc_datetime(service_user_delegation_key.signed_expiry)
156    internal_user_delegation_key.signed_service = service_user_delegation_key.signed_service
157    internal_user_delegation_key.signed_version = service_user_delegation_key.signed_version
158    internal_user_delegation_key.value = service_user_delegation_key.value
159    return internal_user_delegation_key
160