diaspora-event-sdk 0.2.6__py3-none-any.whl → 0.2.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diaspora_event_sdk/sdk/botocore/__init__.py +0 -0
- diaspora_event_sdk/sdk/botocore/auth.py +472 -0
- diaspora_event_sdk/sdk/botocore/awsrequest.py +271 -0
- diaspora_event_sdk/sdk/botocore/compat.py +352 -0
- diaspora_event_sdk/sdk/botocore/credentials.py +66 -0
- diaspora_event_sdk/sdk/botocore/exceptions.py +63 -0
- diaspora_event_sdk/sdk/botocore/utils.py +174 -0
- diaspora_event_sdk/version.py +1 -1
- {diaspora_event_sdk-0.2.6.dist-info → diaspora_event_sdk-0.2.7.dist-info}/METADATA +1 -1
- {diaspora_event_sdk-0.2.6.dist-info → diaspora_event_sdk-0.2.7.dist-info}/RECORD +13 -6
- {diaspora_event_sdk-0.2.6.dist-info → diaspora_event_sdk-0.2.7.dist-info}/LICENSE +0 -0
- {diaspora_event_sdk-0.2.6.dist-info → diaspora_event_sdk-0.2.7.dist-info}/WHEEL +0 -0
- {diaspora_event_sdk-0.2.6.dist-info → diaspora_event_sdk-0.2.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# Copyright (c) 2012-2013 Mitch Garnaat http://garnaat.org/
|
|
2
|
+
# Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
5
|
+
# may not use this file except in compliance with the License. A copy of
|
|
6
|
+
# the License is located at
|
|
7
|
+
#
|
|
8
|
+
# http://aws.amazon.com/apache2.0/
|
|
9
|
+
#
|
|
10
|
+
# or in the "license" file accompanying this file. This file is
|
|
11
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
12
|
+
# ANY KIND, either express or implied. See the License for the specific
|
|
13
|
+
# language governing permissions and limitations under the License.
|
|
14
|
+
# import functools
|
|
15
|
+
import logging
|
|
16
|
+
from collections.abc import Mapping
|
|
17
|
+
|
|
18
|
+
from .compat import (
|
|
19
|
+
HTTPHeaders,
|
|
20
|
+
MutableMapping,
|
|
21
|
+
urlencode,
|
|
22
|
+
urlparse,
|
|
23
|
+
)
|
|
24
|
+
from .exceptions import UnseekableStreamError
|
|
25
|
+
from .utils import determine_content_length
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AWSRequestPreparer:
|
|
31
|
+
"""
|
|
32
|
+
This class performs preparation on AWSRequest objects similar to that of
|
|
33
|
+
the PreparedRequest class does in the requests library. However, the logic
|
|
34
|
+
has been boiled down to meet the specific use cases in botocore. Of note
|
|
35
|
+
there are the following differences:
|
|
36
|
+
This class does not heavily prepare the URL. Requests performed many
|
|
37
|
+
validations and corrections to ensure the URL is properly formatted.
|
|
38
|
+
Botocore either performs these validations elsewhere or otherwise
|
|
39
|
+
consistently provides well formatted URLs.
|
|
40
|
+
|
|
41
|
+
This class does not heavily prepare the body. Body preperation is
|
|
42
|
+
simple and supports only the cases that we document: bytes and
|
|
43
|
+
file-like objects to determine the content-length. This will also
|
|
44
|
+
additionally prepare a body that is a dict to be url encoded params
|
|
45
|
+
string as some signers rely on this. Finally, this class does not
|
|
46
|
+
support multipart file uploads.
|
|
47
|
+
|
|
48
|
+
This class does not prepare the method, auth or cookies.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def prepare(self, original):
|
|
52
|
+
method = original.method
|
|
53
|
+
url = self._prepare_url(original)
|
|
54
|
+
body = self._prepare_body(original)
|
|
55
|
+
headers = self._prepare_headers(original, body)
|
|
56
|
+
stream_output = original.stream_output
|
|
57
|
+
|
|
58
|
+
return AWSPreparedRequest(method, url, headers, body, stream_output)
|
|
59
|
+
|
|
60
|
+
def _prepare_url(self, original):
|
|
61
|
+
url = original.url
|
|
62
|
+
if original.params:
|
|
63
|
+
url_parts = urlparse(url)
|
|
64
|
+
delim = '&' if url_parts.query else '?'
|
|
65
|
+
if isinstance(original.params, Mapping):
|
|
66
|
+
params_to_encode = list(original.params.items())
|
|
67
|
+
else:
|
|
68
|
+
params_to_encode = original.params
|
|
69
|
+
params = urlencode(params_to_encode, doseq=True)
|
|
70
|
+
url = delim.join((url, params))
|
|
71
|
+
return url
|
|
72
|
+
|
|
73
|
+
def _prepare_headers(self, original, prepared_body=None):
|
|
74
|
+
headers = HeadersDict(original.headers.items())
|
|
75
|
+
|
|
76
|
+
# If the transfer encoding or content length is already set, use that
|
|
77
|
+
if 'Transfer-Encoding' in headers or 'Content-Length' in headers:
|
|
78
|
+
return headers
|
|
79
|
+
|
|
80
|
+
# Ensure we set the content length when it is expected
|
|
81
|
+
if original.method not in ('GET', 'HEAD', 'OPTIONS'):
|
|
82
|
+
length = self._determine_content_length(prepared_body)
|
|
83
|
+
if length is not None:
|
|
84
|
+
headers['Content-Length'] = str(length)
|
|
85
|
+
else:
|
|
86
|
+
# Failed to determine content length, using chunked
|
|
87
|
+
# NOTE: This shouldn't ever happen in practice
|
|
88
|
+
body_type = type(prepared_body)
|
|
89
|
+
logger.debug('Failed to determine length of %s', body_type)
|
|
90
|
+
headers['Transfer-Encoding'] = 'chunked'
|
|
91
|
+
|
|
92
|
+
return headers
|
|
93
|
+
|
|
94
|
+
def _to_utf8(self, item):
|
|
95
|
+
key, value = item
|
|
96
|
+
if isinstance(key, str):
|
|
97
|
+
key = key.encode('utf-8')
|
|
98
|
+
if isinstance(value, str):
|
|
99
|
+
value = value.encode('utf-8')
|
|
100
|
+
return key, value
|
|
101
|
+
|
|
102
|
+
def _prepare_body(self, original):
|
|
103
|
+
"""Prepares the given HTTP body data."""
|
|
104
|
+
body = original.data
|
|
105
|
+
if body == b'':
|
|
106
|
+
body = None
|
|
107
|
+
|
|
108
|
+
if isinstance(body, dict):
|
|
109
|
+
params = [self._to_utf8(item) for item in body.items()]
|
|
110
|
+
body = urlencode(params, doseq=True)
|
|
111
|
+
|
|
112
|
+
return body
|
|
113
|
+
|
|
114
|
+
def _determine_content_length(self, body):
|
|
115
|
+
return determine_content_length(body)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class AWSRequest:
|
|
119
|
+
"""Represents the elements of an HTTP request.
|
|
120
|
+
|
|
121
|
+
This class was originally inspired by requests.models.Request, but has been
|
|
122
|
+
boiled down to meet the specific use cases in botocore. That being said this
|
|
123
|
+
class (even in requests) is effectively a named-tuple.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
_REQUEST_PREPARER_CLS = AWSRequestPreparer
|
|
127
|
+
|
|
128
|
+
def __init__(
|
|
129
|
+
self,
|
|
130
|
+
method=None,
|
|
131
|
+
url=None,
|
|
132
|
+
headers=None,
|
|
133
|
+
data=None,
|
|
134
|
+
params=None,
|
|
135
|
+
auth_path=None,
|
|
136
|
+
stream_output=False,
|
|
137
|
+
):
|
|
138
|
+
self._request_preparer = self._REQUEST_PREPARER_CLS()
|
|
139
|
+
|
|
140
|
+
# Default empty dicts for dict params.
|
|
141
|
+
params = {} if params is None else params
|
|
142
|
+
|
|
143
|
+
self.method = method
|
|
144
|
+
self.url = url
|
|
145
|
+
self.headers = HTTPHeaders()
|
|
146
|
+
self.data = data
|
|
147
|
+
self.params = params
|
|
148
|
+
self.auth_path = auth_path
|
|
149
|
+
self.stream_output = stream_output
|
|
150
|
+
|
|
151
|
+
if headers is not None:
|
|
152
|
+
for key, value in headers.items():
|
|
153
|
+
self.headers[key] = value
|
|
154
|
+
|
|
155
|
+
# This is a dictionary to hold information that is used when
|
|
156
|
+
# processing the request. What is inside of ``context`` is open-ended.
|
|
157
|
+
# For example, it may have a timestamp key that is used for holding
|
|
158
|
+
# what the timestamp is when signing the request. Note that none
|
|
159
|
+
# of the information that is inside of ``context`` is directly
|
|
160
|
+
# sent over the wire; the information is only used to assist in
|
|
161
|
+
# creating what is sent over the wire.
|
|
162
|
+
self.context = {}
|
|
163
|
+
|
|
164
|
+
def prepare(self):
|
|
165
|
+
"""Constructs a :class:`AWSPreparedRequest <AWSPreparedRequest>`."""
|
|
166
|
+
return self._request_preparer.prepare(self)
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def body(self):
|
|
170
|
+
body = self.prepare().body
|
|
171
|
+
if isinstance(body, str):
|
|
172
|
+
body = body.encode('utf-8')
|
|
173
|
+
return body
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class AWSPreparedRequest:
|
|
177
|
+
"""A data class representing a finalized request to be sent over the wire.
|
|
178
|
+
|
|
179
|
+
Requests at this stage should be treated as final, and the properties of
|
|
180
|
+
the request should not be modified.
|
|
181
|
+
|
|
182
|
+
:ivar method: The HTTP Method
|
|
183
|
+
:ivar url: The full url
|
|
184
|
+
:ivar headers: The HTTP headers to send.
|
|
185
|
+
:ivar body: The HTTP body.
|
|
186
|
+
:ivar stream_output: If the response for this request should be streamed.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
def __init__(self, method, url, headers, body, stream_output):
|
|
190
|
+
self.method = method
|
|
191
|
+
self.url = url
|
|
192
|
+
self.headers = headers
|
|
193
|
+
self.body = body
|
|
194
|
+
self.stream_output = stream_output
|
|
195
|
+
|
|
196
|
+
def __repr__(self):
|
|
197
|
+
fmt = (
|
|
198
|
+
'<AWSPreparedRequest stream_output=%s, method=%s, url=%s, '
|
|
199
|
+
'headers=%s>'
|
|
200
|
+
)
|
|
201
|
+
return fmt % (self.stream_output, self.method, self.url, self.headers)
|
|
202
|
+
|
|
203
|
+
def reset_stream(self):
|
|
204
|
+
"""Resets the streaming body to it's initial position.
|
|
205
|
+
|
|
206
|
+
If the request contains a streaming body (a streamable file-like object)
|
|
207
|
+
seek to the object's initial position to ensure the entire contents of
|
|
208
|
+
the object is sent. This is a no-op for static bytes-like body types.
|
|
209
|
+
"""
|
|
210
|
+
# Trying to reset a stream when there is a no stream will
|
|
211
|
+
# just immediately return. It's not an error, it will produce
|
|
212
|
+
# the same result as if we had actually reset the stream (we'll send
|
|
213
|
+
# the entire body contents again if we need to).
|
|
214
|
+
# Same case if the body is a string/bytes/bytearray type.
|
|
215
|
+
|
|
216
|
+
non_seekable_types = (bytes, str, bytearray)
|
|
217
|
+
if self.body is None or isinstance(self.body, non_seekable_types):
|
|
218
|
+
return
|
|
219
|
+
try:
|
|
220
|
+
logger.debug("Rewinding stream: %s", self.body)
|
|
221
|
+
self.body.seek(0)
|
|
222
|
+
except Exception as e:
|
|
223
|
+
logger.debug("Unable to rewind stream: %s", e)
|
|
224
|
+
raise UnseekableStreamError(stream_object=self.body)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class _HeaderKey:
|
|
228
|
+
def __init__(self, key):
|
|
229
|
+
self._key = key
|
|
230
|
+
self._lower = key.lower()
|
|
231
|
+
|
|
232
|
+
def __hash__(self):
|
|
233
|
+
return hash(self._lower)
|
|
234
|
+
|
|
235
|
+
def __eq__(self, other):
|
|
236
|
+
return isinstance(other, _HeaderKey) and self._lower == other._lower
|
|
237
|
+
|
|
238
|
+
def __str__(self):
|
|
239
|
+
return self._key
|
|
240
|
+
|
|
241
|
+
def __repr__(self):
|
|
242
|
+
return repr(self._key)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class HeadersDict(MutableMapping):
|
|
246
|
+
"""A case-insenseitive dictionary to represent HTTP headers."""
|
|
247
|
+
|
|
248
|
+
def __init__(self, *args, **kwargs):
|
|
249
|
+
self._dict = {}
|
|
250
|
+
self.update(*args, **kwargs)
|
|
251
|
+
|
|
252
|
+
def __setitem__(self, key, value):
|
|
253
|
+
self._dict[_HeaderKey(key)] = value
|
|
254
|
+
|
|
255
|
+
def __getitem__(self, key):
|
|
256
|
+
return self._dict[_HeaderKey(key)]
|
|
257
|
+
|
|
258
|
+
def __delitem__(self, key):
|
|
259
|
+
del self._dict[_HeaderKey(key)]
|
|
260
|
+
|
|
261
|
+
def __iter__(self):
|
|
262
|
+
return (str(key) for key in self._dict)
|
|
263
|
+
|
|
264
|
+
def __len__(self):
|
|
265
|
+
return len(self._dict)
|
|
266
|
+
|
|
267
|
+
def __repr__(self):
|
|
268
|
+
return repr(self._dict)
|
|
269
|
+
|
|
270
|
+
def copy(self):
|
|
271
|
+
return HeadersDict(self.items())
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
|
5
|
+
# the License is located at
|
|
6
|
+
#
|
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
|
8
|
+
#
|
|
9
|
+
# or in the "license" file accompanying this file. This file is
|
|
10
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
11
|
+
# ANY KIND, either express or implied. See the License for the specific
|
|
12
|
+
# language governing permissions and limitations under the License.
|
|
13
|
+
|
|
14
|
+
from urllib.parse import (
|
|
15
|
+
quote,
|
|
16
|
+
urlencode,
|
|
17
|
+
unquote,
|
|
18
|
+
unquote_plus,
|
|
19
|
+
urlparse,
|
|
20
|
+
urlsplit,
|
|
21
|
+
urlunsplit,
|
|
22
|
+
urljoin,
|
|
23
|
+
parse_qsl,
|
|
24
|
+
parse_qs,
|
|
25
|
+
)
|
|
26
|
+
import json
|
|
27
|
+
from itertools import zip_longest
|
|
28
|
+
from email.utils import formatdate
|
|
29
|
+
from base64 import encodebytes
|
|
30
|
+
from io import IOBase as _IOBase
|
|
31
|
+
from http.client import HTTPResponse
|
|
32
|
+
import copy
|
|
33
|
+
import datetime
|
|
34
|
+
import sys
|
|
35
|
+
import inspect
|
|
36
|
+
import warnings
|
|
37
|
+
import hashlib
|
|
38
|
+
from http.client import HTTPMessage
|
|
39
|
+
import logging
|
|
40
|
+
import shlex
|
|
41
|
+
import re
|
|
42
|
+
import os
|
|
43
|
+
from collections import OrderedDict
|
|
44
|
+
from collections.abc import MutableMapping
|
|
45
|
+
from math import floor
|
|
46
|
+
|
|
47
|
+
from dateutil.tz import tzlocal
|
|
48
|
+
from urllib3 import exceptions
|
|
49
|
+
|
|
50
|
+
from .exceptions import MD5UnavailableError
|
|
51
|
+
|
|
52
|
+
logger = logging.getLogger(__name__)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class HTTPHeaders(HTTPMessage):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
file_type = _IOBase
|
|
60
|
+
zip = zip
|
|
61
|
+
|
|
62
|
+
# In python3, unquote takes a str() object, url decodes it,
|
|
63
|
+
# then takes the bytestring and decodes it to utf-8.
|
|
64
|
+
unquote_str = unquote_plus
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def set_socket_timeout(http_response, timeout):
|
|
68
|
+
"""Set the timeout of the socket from an HTTPResponse.
|
|
69
|
+
|
|
70
|
+
:param http_response: An instance of ``httplib.HTTPResponse``
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
http_response._fp.fp.raw._sock.settimeout(timeout)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def accepts_kwargs(func):
|
|
77
|
+
# In python3.4.1, there's backwards incompatible
|
|
78
|
+
# changes when using getargspec with functools.partials.
|
|
79
|
+
return inspect.getfullargspec(func)[2]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def ensure_unicode(s, encoding=None, errors=None):
|
|
83
|
+
# NOOP in Python 3, because every string is already unicode
|
|
84
|
+
return s
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def ensure_bytes(s, encoding='utf-8', errors='strict'):
|
|
88
|
+
if isinstance(s, str):
|
|
89
|
+
return s.encode(encoding, errors)
|
|
90
|
+
if isinstance(s, bytes):
|
|
91
|
+
return s
|
|
92
|
+
raise ValueError(f"Expected str or bytes, received {type(s)}.")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
import xml.etree.cElementTree as ETree
|
|
97
|
+
except ImportError:
|
|
98
|
+
# cElementTree does not exist from Python3.9+
|
|
99
|
+
import xml.etree.ElementTree as ETree
|
|
100
|
+
XMLParseError = ETree.ParseError
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def filter_ssl_warnings():
|
|
104
|
+
# Ignore warnings related to SNI as it is not being used in validations.
|
|
105
|
+
warnings.filterwarnings(
|
|
106
|
+
'ignore',
|
|
107
|
+
message="A true SSLContext object is not available.*",
|
|
108
|
+
category=exceptions.InsecurePlatformWarning,
|
|
109
|
+
module=r".*urllib3\.util\.ssl_",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def from_dict(cls, d):
|
|
115
|
+
new_instance = cls()
|
|
116
|
+
for key, value in d.items():
|
|
117
|
+
new_instance[key] = value
|
|
118
|
+
return new_instance
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def from_pairs(cls, pairs):
|
|
123
|
+
new_instance = cls()
|
|
124
|
+
for key, value in pairs:
|
|
125
|
+
new_instance[key] = value
|
|
126
|
+
return new_instance
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
HTTPHeaders.from_dict = from_dict
|
|
130
|
+
HTTPHeaders.from_pairs = from_pairs
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def copy_kwargs(kwargs):
|
|
134
|
+
"""
|
|
135
|
+
This used to be a compat shim for 2.6 but is now just an alias.
|
|
136
|
+
"""
|
|
137
|
+
copy_kwargs = copy.copy(kwargs)
|
|
138
|
+
return copy_kwargs
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def total_seconds(delta):
|
|
142
|
+
"""
|
|
143
|
+
Returns the total seconds in a ``datetime.timedelta``.
|
|
144
|
+
|
|
145
|
+
This used to be a compat shim for 2.6 but is now just an alias.
|
|
146
|
+
|
|
147
|
+
:param delta: The timedelta object
|
|
148
|
+
:type delta: ``datetime.timedelta``
|
|
149
|
+
"""
|
|
150
|
+
return delta.total_seconds()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# Checks to see if md5 is available on this system. A given system might not
|
|
154
|
+
# have access to it for various reasons, such as FIPS mode being enabled.
|
|
155
|
+
try:
|
|
156
|
+
hashlib.md5()
|
|
157
|
+
MD5_AVAILABLE = True
|
|
158
|
+
except ValueError:
|
|
159
|
+
MD5_AVAILABLE = False
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def get_md5(*args, **kwargs):
|
|
163
|
+
"""
|
|
164
|
+
Attempts to get an md5 hashing object.
|
|
165
|
+
|
|
166
|
+
:param args: Args to pass to the MD5 constructor
|
|
167
|
+
:param kwargs: Key word arguments to pass to the MD5 constructor
|
|
168
|
+
:return: An MD5 hashing object if available. If it is unavailable, None
|
|
169
|
+
is returned if raise_error_if_unavailable is set to False.
|
|
170
|
+
"""
|
|
171
|
+
if MD5_AVAILABLE:
|
|
172
|
+
return hashlib.md5(*args, **kwargs)
|
|
173
|
+
else:
|
|
174
|
+
raise MD5UnavailableError()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def compat_shell_split(s, platform=None):
|
|
178
|
+
if platform is None:
|
|
179
|
+
platform = sys.platform
|
|
180
|
+
|
|
181
|
+
if platform == "win32":
|
|
182
|
+
return _windows_shell_split(s)
|
|
183
|
+
else:
|
|
184
|
+
return shlex.split(s)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _windows_shell_split(s):
|
|
188
|
+
"""Splits up a windows command as the built-in command parser would.
|
|
189
|
+
|
|
190
|
+
Windows has potentially bizarre rules depending on where you look. When
|
|
191
|
+
spawning a process via the Windows C runtime (which is what python does
|
|
192
|
+
when you call popen) the rules are as follows:
|
|
193
|
+
|
|
194
|
+
https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments
|
|
195
|
+
|
|
196
|
+
To summarize:
|
|
197
|
+
|
|
198
|
+
* Only space and tab are valid delimiters
|
|
199
|
+
* Double quotes are the only valid quotes
|
|
200
|
+
* Backslash is interpreted literally unless it is part of a chain that
|
|
201
|
+
leads up to a double quote. Then the backslashes escape the backslashes,
|
|
202
|
+
and if there is an odd number the final backslash escapes the quote.
|
|
203
|
+
|
|
204
|
+
:param s: The command string to split up into parts.
|
|
205
|
+
:return: A list of command components.
|
|
206
|
+
"""
|
|
207
|
+
if not s:
|
|
208
|
+
return []
|
|
209
|
+
|
|
210
|
+
components = []
|
|
211
|
+
buff = []
|
|
212
|
+
is_quoted = False
|
|
213
|
+
num_backslashes = 0
|
|
214
|
+
for character in s:
|
|
215
|
+
if character == '\\':
|
|
216
|
+
# We can't simply append backslashes because we don't know if
|
|
217
|
+
# they are being used as escape characters or not. Instead we
|
|
218
|
+
# keep track of how many we've encountered and handle them when
|
|
219
|
+
# we encounter a different character.
|
|
220
|
+
num_backslashes += 1
|
|
221
|
+
elif character == '"':
|
|
222
|
+
if num_backslashes > 0:
|
|
223
|
+
# The backslashes are in a chain leading up to a double
|
|
224
|
+
# quote, so they are escaping each other.
|
|
225
|
+
buff.append('\\' * int(floor(num_backslashes / 2)))
|
|
226
|
+
remainder = num_backslashes % 2
|
|
227
|
+
num_backslashes = 0
|
|
228
|
+
if remainder == 1:
|
|
229
|
+
# The number of backslashes is uneven, so they are also
|
|
230
|
+
# escaping the double quote, so it needs to be added to
|
|
231
|
+
# the current component buffer.
|
|
232
|
+
buff.append('"')
|
|
233
|
+
continue
|
|
234
|
+
|
|
235
|
+
# We've encountered a double quote that is not escaped,
|
|
236
|
+
# so we toggle is_quoted.
|
|
237
|
+
is_quoted = not is_quoted
|
|
238
|
+
|
|
239
|
+
# If there are quotes, then we may want an empty string. To be
|
|
240
|
+
# safe, we add an empty string to the buffer so that we make
|
|
241
|
+
# sure it sticks around if there's nothing else between quotes.
|
|
242
|
+
# If there is other stuff between quotes, the empty string will
|
|
243
|
+
# disappear during the joining process.
|
|
244
|
+
buff.append('')
|
|
245
|
+
elif character in [' ', '\t'] and not is_quoted:
|
|
246
|
+
# Since the backslashes aren't leading up to a quote, we put in
|
|
247
|
+
# the exact number of backslashes.
|
|
248
|
+
if num_backslashes > 0:
|
|
249
|
+
buff.append('\\' * num_backslashes)
|
|
250
|
+
num_backslashes = 0
|
|
251
|
+
|
|
252
|
+
# Excess whitespace is ignored, so only add the components list
|
|
253
|
+
# if there is anything in the buffer.
|
|
254
|
+
if buff:
|
|
255
|
+
components.append(''.join(buff))
|
|
256
|
+
buff = []
|
|
257
|
+
else:
|
|
258
|
+
# Since the backslashes aren't leading up to a quote, we put in
|
|
259
|
+
# the exact number of backslashes.
|
|
260
|
+
if num_backslashes > 0:
|
|
261
|
+
buff.append('\\' * num_backslashes)
|
|
262
|
+
num_backslashes = 0
|
|
263
|
+
buff.append(character)
|
|
264
|
+
|
|
265
|
+
# Quotes must be terminated.
|
|
266
|
+
if is_quoted:
|
|
267
|
+
raise ValueError(f"No closing quotation in string: {s}")
|
|
268
|
+
|
|
269
|
+
# There may be some leftover backslashes, so we need to add them in.
|
|
270
|
+
# There's no quote so we add the exact number.
|
|
271
|
+
if num_backslashes > 0:
|
|
272
|
+
buff.append('\\' * num_backslashes)
|
|
273
|
+
|
|
274
|
+
# Add the final component in if there is anything in the buffer.
|
|
275
|
+
if buff:
|
|
276
|
+
components.append(''.join(buff))
|
|
277
|
+
|
|
278
|
+
return components
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def get_tzinfo_options():
|
|
282
|
+
# Due to dateutil/dateutil#197, Windows may fail to parse times in the past
|
|
283
|
+
# with the system clock. We can alternatively fallback to tzwininfo when
|
|
284
|
+
# this happens, which will get time info from the Windows registry.
|
|
285
|
+
if sys.platform == 'win32':
|
|
286
|
+
from dateutil.tz import tzwinlocal
|
|
287
|
+
|
|
288
|
+
return (tzlocal, tzwinlocal)
|
|
289
|
+
else:
|
|
290
|
+
return (tzlocal,)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# Detect if CRT is available for use
|
|
294
|
+
try:
|
|
295
|
+
import awscrt.auth
|
|
296
|
+
|
|
297
|
+
# Allow user opt-out if needed
|
|
298
|
+
disabled = os.environ.get('BOTO_DISABLE_CRT', "false")
|
|
299
|
+
HAS_CRT = not disabled.lower() == 'true'
|
|
300
|
+
except ImportError:
|
|
301
|
+
HAS_CRT = False
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
########################################################
|
|
305
|
+
# urllib3 compat backports #
|
|
306
|
+
########################################################
|
|
307
|
+
|
|
308
|
+
# Vendoring IPv6 validation regex patterns from urllib3
|
|
309
|
+
# https://github.com/urllib3/urllib3/blob/7e856c0/src/urllib3/util/url.py
|
|
310
|
+
IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}"
|
|
311
|
+
IPV4_RE = re.compile("^" + IPV4_PAT + "$")
|
|
312
|
+
HEX_PAT = "[0-9A-Fa-f]{1,4}"
|
|
313
|
+
LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=HEX_PAT, ipv4=IPV4_PAT)
|
|
314
|
+
_subs = {"hex": HEX_PAT, "ls32": LS32_PAT}
|
|
315
|
+
_variations = [
|
|
316
|
+
# 6( h16 ":" ) ls32
|
|
317
|
+
"(?:%(hex)s:){6}%(ls32)s",
|
|
318
|
+
# "::" 5( h16 ":" ) ls32
|
|
319
|
+
"::(?:%(hex)s:){5}%(ls32)s",
|
|
320
|
+
# [ h16 ] "::" 4( h16 ":" ) ls32
|
|
321
|
+
"(?:%(hex)s)?::(?:%(hex)s:){4}%(ls32)s",
|
|
322
|
+
# [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
|
|
323
|
+
"(?:(?:%(hex)s:)?%(hex)s)?::(?:%(hex)s:){3}%(ls32)s",
|
|
324
|
+
# [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
|
|
325
|
+
"(?:(?:%(hex)s:){0,2}%(hex)s)?::(?:%(hex)s:){2}%(ls32)s",
|
|
326
|
+
# [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
|
|
327
|
+
"(?:(?:%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s",
|
|
328
|
+
# [ *4( h16 ":" ) h16 ] "::" ls32
|
|
329
|
+
"(?:(?:%(hex)s:){0,4}%(hex)s)?::%(ls32)s",
|
|
330
|
+
# [ *5( h16 ":" ) h16 ] "::" h16
|
|
331
|
+
"(?:(?:%(hex)s:){0,5}%(hex)s)?::%(hex)s",
|
|
332
|
+
# [ *6( h16 ":" ) h16 ] "::"
|
|
333
|
+
"(?:(?:%(hex)s:){0,6}%(hex)s)?::",
|
|
334
|
+
]
|
|
335
|
+
|
|
336
|
+
UNRESERVED_PAT = (
|
|
337
|
+
r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._!\-~"
|
|
338
|
+
)
|
|
339
|
+
IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")"
|
|
340
|
+
ZONE_ID_PAT = "(?:%25|%)(?:[" + UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+"
|
|
341
|
+
IPV6_ADDRZ_PAT = r"\[" + IPV6_PAT + r"(?:" + ZONE_ID_PAT + r")?\]"
|
|
342
|
+
IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT + "$")
|
|
343
|
+
|
|
344
|
+
# These are the characters that are stripped by post-bpo-43882 urlparse().
|
|
345
|
+
UNSAFE_URL_CHARS = frozenset('\t\r\n')
|
|
346
|
+
|
|
347
|
+
# Detect if gzip is available for use
|
|
348
|
+
try:
|
|
349
|
+
import gzip
|
|
350
|
+
HAS_GZIP = True
|
|
351
|
+
except ImportError:
|
|
352
|
+
HAS_GZIP = False
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Copyright (c) 2012-2013 Mitch Garnaat http://garnaat.org/
|
|
2
|
+
# Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
5
|
+
# may not use this file except in compliance with the License. A copy of
|
|
6
|
+
# the License is located at
|
|
7
|
+
#
|
|
8
|
+
# http://aws.amazon.com/apache2.0/
|
|
9
|
+
#
|
|
10
|
+
# or in the "license" file accompanying this file. This file is
|
|
11
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
12
|
+
# ANY KIND, either express or implied. See the License for the specific
|
|
13
|
+
# language governing permissions and limitations under the License.
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from collections import namedtuple
|
|
17
|
+
|
|
18
|
+
from .compat import ensure_unicode
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
ReadOnlyCredentials = namedtuple(
|
|
22
|
+
'ReadOnlyCredentials', ['access_key', 'secret_key', 'token']
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
_DEFAULT_MANDATORY_REFRESH_TIMEOUT = 10 * 60 # 10 min
|
|
26
|
+
_DEFAULT_ADVISORY_REFRESH_TIMEOUT = 15 * 60 # 15 min
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Credentials:
|
|
30
|
+
"""
|
|
31
|
+
Holds the credentials needed to authenticate requests.
|
|
32
|
+
|
|
33
|
+
:param str access_key: The access key part of the credentials.
|
|
34
|
+
:param str secret_key: The secret key part of the credentials.
|
|
35
|
+
:param str token: The security token, valid only for session credentials.
|
|
36
|
+
:param str method: A string which identifies where the credentials
|
|
37
|
+
were found.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, access_key, secret_key, token=None, method=None):
|
|
41
|
+
self.access_key = access_key
|
|
42
|
+
self.secret_key = secret_key
|
|
43
|
+
self.token = token
|
|
44
|
+
|
|
45
|
+
if method is None:
|
|
46
|
+
method = 'explicit'
|
|
47
|
+
self.method = method
|
|
48
|
+
|
|
49
|
+
self._normalize()
|
|
50
|
+
|
|
51
|
+
def _normalize(self):
|
|
52
|
+
# Keys would sometimes (accidentally) contain non-ascii characters.
|
|
53
|
+
# It would cause a confusing UnicodeDecodeError in Python 2.
|
|
54
|
+
# We explicitly convert them into unicode to avoid such error.
|
|
55
|
+
#
|
|
56
|
+
# Eventually the service will decide whether to accept the credential.
|
|
57
|
+
# This also complies with the behavior in Python 3.
|
|
58
|
+
# self.access_key = botocore.compat.ensure_unicode(self.access_key)
|
|
59
|
+
# self.secret_key = botocore.compat.ensure_unicode(self.secret_key)
|
|
60
|
+
self.access_key = ensure_unicode(self.access_key)
|
|
61
|
+
self.secret_key = ensure_unicode(self.secret_key)
|
|
62
|
+
|
|
63
|
+
def get_frozen_credentials(self):
|
|
64
|
+
return ReadOnlyCredentials(
|
|
65
|
+
self.access_key, self.secret_key, self.token
|
|
66
|
+
)
|