| OLD | NEW |
| 1 # Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/ | 1 # Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/ |
| 2 # Copyright (c) 2010, Eucalyptus Systems, Inc. | 2 # Copyright (c) 2010, Eucalyptus Systems, Inc. |
| 3 # All rights reserved. | 3 # All rights reserved. |
| 4 # | 4 # |
| 5 # Permission is hereby granted, free of charge, to any person obtaining a | 5 # Permission is hereby granted, free of charge, to any person obtaining a |
| 6 # copy of this software and associated documentation files (the | 6 # copy of this software and associated documentation files (the |
| 7 # "Software"), to deal in the Software without restriction, including | 7 # "Software"), to deal in the Software without restriction, including |
| 8 # without limitation the rights to use, copy, modify, merge, publish, dis- | 8 # without limitation the rights to use, copy, modify, merge, publish, dis- |
| 9 # tribute, sublicense, and/or sell copies of the Software, and to permit | 9 # tribute, sublicense, and/or sell copies of the Software, and to permit |
| 10 # persons to whom the Software is furnished to do so, subject to the fol- | 10 # persons to whom the Software is furnished to do so, subject to the fol- |
| 11 # lowing conditions: | 11 # lowing conditions: |
| 12 # | 12 # |
| 13 # The above copyright notice and this permission notice shall be included | 13 # The above copyright notice and this permission notice shall be included |
| 14 # in all copies or substantial portions of the Software. | 14 # in all copies or substantial portions of the Software. |
| 15 # | 15 # |
| 16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | 16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| 17 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- | 17 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
| 18 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT | 18 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
| 19 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | 19 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 20 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 20 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | 21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 22 # IN THE SOFTWARE. | 22 # IN THE SOFTWARE. |
| 23 | 23 |
| 24 import xml.sax | 24 import xml.sax |
| 25 import urllib, base64 | 25 import urllib, base64 |
| 26 import time | 26 import time |
| 27 import boto.utils | 27 import boto.utils |
| 28 from boto.connection import AWSAuthConnection | 28 from boto.connection import AWSAuthConnection |
| 29 from boto import handler | 29 from boto import handler |
| 30 from boto.provider import Provider | |
| 31 from boto.s3.bucket import Bucket | 30 from boto.s3.bucket import Bucket |
| 32 from boto.s3.key import Key | 31 from boto.s3.key import Key |
| 33 from boto.resultset import ResultSet | 32 from boto.resultset import ResultSet |
| 34 from boto.exception import BotoClientError | 33 from boto.exception import BotoClientError |
| 35 | 34 |
| 36 def check_lowercase_bucketname(n): | 35 def check_lowercase_bucketname(n): |
| 37 """ | 36 """ |
| 38 Bucket names must not contain uppercase characters. We check for | 37 Bucket names must not contain uppercase characters. We check for |
| 39 this by appending a lowercase character and testing with islower(). | 38 this by appending a lowercase character and testing with islower(). |
| 40 Note this also covers cases like numeric bucket names with dashes. | 39 Note this also covers cases like numeric bucket names with dashes. |
| (...skipping 16 matching lines...) Expand all Loading... |
| 57 "hosting calling format.") | 56 "hosting calling format.") |
| 58 return True | 57 return True |
| 59 | 58 |
| 60 def assert_case_insensitive(f): | 59 def assert_case_insensitive(f): |
| 61 def wrapper(*args, **kwargs): | 60 def wrapper(*args, **kwargs): |
| 62 if len(args) == 3 and check_lowercase_bucketname(args[2]): | 61 if len(args) == 3 and check_lowercase_bucketname(args[2]): |
| 63 pass | 62 pass |
| 64 return f(*args, **kwargs) | 63 return f(*args, **kwargs) |
| 65 return wrapper | 64 return wrapper |
| 66 | 65 |
| 67 class _CallingFormat: | 66 class _CallingFormat(object): |
| 67 |
| 68 def get_bucket_server(self, server, bucket): |
| 69 return '' |
| 68 | 70 |
| 69 def build_url_base(self, connection, protocol, server, bucket, key=''): | 71 def build_url_base(self, connection, protocol, server, bucket, key=''): |
| 70 url_base = '%s://' % protocol | 72 url_base = '%s://' % protocol |
| 71 url_base += self.build_host(server, bucket) | 73 url_base += self.build_host(server, bucket) |
| 72 url_base += connection.get_path(self.build_path_base(bucket, key)) | 74 url_base += connection.get_path(self.build_path_base(bucket, key)) |
| 73 return url_base | 75 return url_base |
| 74 | 76 |
| 75 def build_host(self, server, bucket): | 77 def build_host(self, server, bucket): |
| 76 if bucket == '': | 78 if bucket == '': |
| 77 return server | 79 return server |
| 78 else: | 80 else: |
| 79 return self.get_bucket_server(server, bucket) | 81 return self.get_bucket_server(server, bucket) |
| 80 | 82 |
| 81 def build_auth_path(self, bucket, key=''): | 83 def build_auth_path(self, bucket, key=''): |
| 84 key = boto.utils.get_utf8_value(key) |
| 82 path = '' | 85 path = '' |
| 83 if bucket != '': | 86 if bucket != '': |
| 84 path = '/' + bucket | 87 path = '/' + bucket |
| 85 return path + '/%s' % urllib.quote(key) | 88 return path + '/%s' % urllib.quote(key) |
| 86 | 89 |
| 87 def build_path_base(self, bucket, key=''): | 90 def build_path_base(self, bucket, key=''): |
| 91 key = boto.utils.get_utf8_value(key) |
| 88 return '/%s' % urllib.quote(key) | 92 return '/%s' % urllib.quote(key) |
| 89 | 93 |
| 90 class SubdomainCallingFormat(_CallingFormat): | 94 class SubdomainCallingFormat(_CallingFormat): |
| 91 | 95 |
| 92 @assert_case_insensitive | 96 @assert_case_insensitive |
| 93 def get_bucket_server(self, server, bucket): | 97 def get_bucket_server(self, server, bucket): |
| 94 return '%s.%s' % (bucket, server) | 98 return '%s.%s' % (bucket, server) |
| 95 | 99 |
| 96 class VHostCallingFormat(_CallingFormat): | 100 class VHostCallingFormat(_CallingFormat): |
| 97 | 101 |
| 98 @assert_case_insensitive | 102 @assert_case_insensitive |
| 99 def get_bucket_server(self, server, bucket): | 103 def get_bucket_server(self, server, bucket): |
| 100 return bucket | 104 return bucket |
| 101 | 105 |
| 102 class OrdinaryCallingFormat(_CallingFormat): | 106 class OrdinaryCallingFormat(_CallingFormat): |
| 103 | 107 |
| 104 def get_bucket_server(self, server, bucket): | 108 def get_bucket_server(self, server, bucket): |
| 105 return server | 109 return server |
| 106 | 110 |
| 107 def build_path_base(self, bucket, key=''): | 111 def build_path_base(self, bucket, key=''): |
| 112 key = boto.utils.get_utf8_value(key) |
| 108 path_base = '/' | 113 path_base = '/' |
| 109 if bucket: | 114 if bucket: |
| 110 path_base += "%s/" % bucket | 115 path_base += "%s/" % bucket |
| 111 return path_base + urllib.quote(key) | 116 return path_base + urllib.quote(key) |
| 112 | 117 |
| 118 class ProtocolIndependentOrdinaryCallingFormat(OrdinaryCallingFormat): |
| 119 |
| 120 def build_url_base(self, connection, protocol, server, bucket, key=''): |
| 121 url_base = '//' |
| 122 url_base += self.build_host(server, bucket) |
| 123 url_base += connection.get_path(self.build_path_base(bucket, key)) |
| 124 return url_base |
| 125 |
| 113 class Location: | 126 class Location: |
| 114 DEFAULT = '' # US Classic Region | 127 DEFAULT = '' # US Classic Region |
| 115 EU = 'EU' | 128 EU = 'EU' |
| 116 USWest = 'us-west-1' | 129 USWest = 'us-west-1' |
| 130 APNortheast = 'ap-northeast-1' |
| 117 APSoutheast = 'ap-southeast-1' | 131 APSoutheast = 'ap-southeast-1' |
| 118 | 132 |
| 119 class S3Connection(AWSAuthConnection): | 133 class S3Connection(AWSAuthConnection): |
| 120 | 134 |
| 121 DefaultHost = 's3.amazonaws.com' | 135 DefaultHost = 's3.amazonaws.com' |
| 122 QueryString = 'Signature=%s&Expires=%d&AWSAccessKeyId=%s' | 136 QueryString = 'Signature=%s&Expires=%d&AWSAccessKeyId=%s' |
| 123 | 137 |
| 124 def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, | 138 def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, |
| 125 is_secure=True, port=None, proxy=None, proxy_port=None, | 139 is_secure=True, port=None, proxy=None, proxy_port=None, |
| 126 proxy_user=None, proxy_pass=None, | 140 proxy_user=None, proxy_pass=None, |
| 127 host=DefaultHost, debug=0, https_connection_factory=None, | 141 host=DefaultHost, debug=0, https_connection_factory=None, |
| 128 calling_format=SubdomainCallingFormat(), path='/', provider='aw
s', | 142 calling_format=SubdomainCallingFormat(), path='/', |
| 129 bucket_class=Bucket): | 143 provider='aws', bucket_class=Bucket, security_token=None): |
| 130 self.calling_format = calling_format | 144 self.calling_format = calling_format |
| 131 self.bucket_class = bucket_class | 145 self.bucket_class = bucket_class |
| 132 AWSAuthConnection.__init__(self, host, | 146 AWSAuthConnection.__init__(self, host, |
| 133 aws_access_key_id, aws_secret_access_key, | 147 aws_access_key_id, aws_secret_access_key, |
| 134 is_secure, port, proxy, proxy_port, proxy_user, proxy_pass, | 148 is_secure, port, proxy, proxy_port, proxy_user, proxy_pass, |
| 135 debug=debug, https_connection_factory=https_connection_factory, | 149 debug=debug, https_connection_factory=https_connection_factory, |
| 136 path=path, provider=provider) | 150 path=path, provider=provider, security_token=security_token) |
| 137 | 151 |
| 138 def _required_auth_capability(self): | 152 def _required_auth_capability(self): |
| 139 return ['s3'] | 153 return ['s3'] |
| 140 | 154 |
| 141 def __iter__(self): | 155 def __iter__(self): |
| 142 for bucket in self.get_all_buckets(): | 156 for bucket in self.get_all_buckets(): |
| 143 yield bucket | 157 yield bucket |
| 144 | 158 |
| 145 def __contains__(self, bucket_name): | 159 def __contains__(self, bucket_name): |
| 146 return not (self.lookup(bucket_name) is None) | 160 return not (self.lookup(bucket_name) is None) |
| 147 | 161 |
| 148 def set_bucket_class(self, bucket_class): | 162 def set_bucket_class(self, bucket_class): |
| 149 """ | 163 """ |
| 150 Set the Bucket class associated with this bucket. By default, this | 164 Set the Bucket class associated with this bucket. By default, this |
| 151 would be the boto.s3.key.Bucket class but if you want to subclass that | 165 would be the boto.s3.key.Bucket class but if you want to subclass that |
| 152 for some reason this allows you to associate your new class. | 166 for some reason this allows you to associate your new class. |
| 153 | 167 |
| 154 :type bucket_class: class | 168 :type bucket_class: class |
| 155 :param bucket_class: A subclass of Bucket that can be more specific | 169 :param bucket_class: A subclass of Bucket that can be more specific |
| 156 """ | 170 """ |
| 157 self.bucket_class = bucket_class | 171 self.bucket_class = bucket_class |
| 158 | 172 |
| 159 def build_post_policy(self, expiration_time, conditions): | 173 def build_post_policy(self, expiration_time, conditions): |
| 160 """ | 174 """ |
| 161 Taken from the AWS book Python examples and modified for use with boto | 175 Taken from the AWS book Python examples and modified for use with boto |
| 162 """ | 176 """ |
| 163 assert type(expiration_time) == time.struct_time, \ | 177 assert type(expiration_time) == time.struct_time, \ |
| 164 'Policy document must include a valid expiration Time object' | 178 'Policy document must include a valid expiration Time object' |
| 165 | 179 |
| 166 # Convert conditions object mappings to condition statements | 180 # Convert conditions object mappings to condition statements |
| 167 | 181 |
| 168 return '{"expiration": "%s",\n"conditions": [%s]}' % \ | 182 return '{"expiration": "%s",\n"conditions": [%s]}' % \ |
| 169 (time.strftime(boto.utils.ISO8601, expiration_time), ",".join(condit
ions)) | 183 (time.strftime(boto.utils.ISO8601, expiration_time), ",".join(condit
ions)) |
| 170 | 184 |
| 171 | 185 |
| 172 def build_post_form_args(self, bucket_name, key, expires_in = 6000, | 186 def build_post_form_args(self, bucket_name, key, expires_in = 6000, |
| 173 acl = None, success_action_redirect = None, max_content_
length = None, | 187 acl = None, success_action_redirect = None, |
| 174 http_method = "http", fields=None, conditions=None): | 188 max_content_length = None, |
| 189 http_method = "http", fields=None, |
| 190 conditions=None): |
| 175 """ | 191 """ |
| 176 Taken from the AWS book Python examples and modified for use with boto | 192 Taken from the AWS book Python examples and modified for use with boto |
| 177 This only returns the arguments required for the post form, not the actu
al form | 193 This only returns the arguments required for the post form, not the actu
al form |
| 178 This does not return the file input field which also needs to be added | 194 This does not return the file input field which also needs to be added |
| 179 | 195 |
| 180 :param bucket_name: Bucket to submit to | 196 :param bucket_name: Bucket to submit to |
| 181 :type bucket_name: string | 197 :type bucket_name: string |
| 182 | 198 |
| 183 :param key: Key name, optionally add ${filename} to the end to attach t
he submitted filename | 199 :param key: Key name, optionally add ${filename} to the end to attach t
he submitted filename |
| 184 :type key: string | 200 :type key: string |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 254 signature = self._auth_handler.sign_string(policy_b64) | 270 signature = self._auth_handler.sign_string(policy_b64) |
| 255 fields.append({"name": "signature", "value": signature}) | 271 fields.append({"name": "signature", "value": signature}) |
| 256 fields.append({"name": "key", "value": key}) | 272 fields.append({"name": "key", "value": key}) |
| 257 | 273 |
| 258 # HTTPS protocol will be used if the secure HTTP option is enabled. | 274 # HTTPS protocol will be used if the secure HTTP option is enabled. |
| 259 url = '%s://%s/' % (http_method, self.calling_format.build_host(self.ser
ver_name(), bucket_name)) | 275 url = '%s://%s/' % (http_method, self.calling_format.build_host(self.ser
ver_name(), bucket_name)) |
| 260 | 276 |
| 261 return {"action": url, "fields": fields} | 277 return {"action": url, "fields": fields} |
| 262 | 278 |
| 263 | 279 |
| 264 def generate_url(self, expires_in, method, bucket='', key='', | 280 def generate_url(self, expires_in, method, bucket='', key='', headers=None, |
| 265 headers=None, query_auth=True, force_http=False): | 281 query_auth=True, force_http=False, response_headers=None): |
| 266 if not headers: | 282 if not headers: |
| 267 headers = {} | 283 headers = {} |
| 268 expires = int(time.time() + expires_in) | 284 expires = int(time.time() + expires_in) |
| 269 auth_path = self.calling_format.build_auth_path(bucket, key) | 285 auth_path = self.calling_format.build_auth_path(bucket, key) |
| 270 auth_path = self.get_path(auth_path) | 286 auth_path = self.get_path(auth_path) |
| 287 # Arguments to override response headers become part of the canonical |
| 288 # string to be signed. |
| 289 if response_headers: |
| 290 response_hdrs = ["%s=%s" % (k, v) for k, v in |
| 291 response_headers.items()] |
| 292 delimiter = '?' if '?' not in auth_path else '&' |
| 293 auth_path = "%s%s%s" % (auth_path, delimiter, '&'.join(response_hdrs
)) |
| 294 else: |
| 295 response_headers = {} |
| 271 c_string = boto.utils.canonical_string(method, auth_path, headers, | 296 c_string = boto.utils.canonical_string(method, auth_path, headers, |
| 272 expires, self.provider) | 297 expires, self.provider) |
| 273 b64_hmac = self._auth_handler.sign_string(c_string) | 298 b64_hmac = self._auth_handler.sign_string(c_string) |
| 274 encoded_canonical = urllib.quote_plus(b64_hmac) | 299 encoded_canonical = urllib.quote_plus(b64_hmac) |
| 275 self.calling_format.build_path_base(bucket, key) | 300 self.calling_format.build_path_base(bucket, key) |
| 276 if query_auth: | 301 if query_auth: |
| 277 query_part = '?' + self.QueryString % (encoded_canonical, expires, | 302 query_part = '?' + self.QueryString % (encoded_canonical, expires, |
| 278 self.aws_access_key_id) | 303 self.aws_access_key_id) |
| 279 sec_hdr = self.provider.security_token_header | 304 # The response headers must also be GET parameters in the URL. |
| 280 if sec_hdr in headers: | 305 headers.update(response_headers) |
| 281 query_part += ('&%s=%s' % (sec_hdr, | 306 hdrs = [ '%s=%s'%(name, urllib.quote(val)) for name,val in headers.i
tems() ] |
| 282 urllib.quote(headers[sec_hdr]))); | 307 q_str = '&'.join(hdrs) |
| 308 if q_str: |
| 309 query_part += '&' + q_str |
| 283 else: | 310 else: |
| 284 query_part = '' | 311 query_part = '' |
| 285 if force_http: | 312 if force_http: |
| 286 protocol = 'http' | 313 protocol = 'http' |
| 287 port = 80 | 314 port = 80 |
| 288 else: | 315 else: |
| 289 protocol = self.protocol | 316 protocol = self.protocol |
| 290 port = self.port | 317 port = self.port |
| 291 return self.calling_format.build_url_base(self, protocol, self.server_na
me(port), | 318 return self.calling_format.build_url_base(self, protocol, self.server_na
me(port), |
| 292 bucket, key) + query_part | 319 bucket, key) + query_part |
| 293 | 320 |
| 294 def get_all_buckets(self, headers=None): | 321 def get_all_buckets(self, headers=None): |
| 295 response = self.make_request('GET', headers=headers) | 322 response = self.make_request('GET', headers=headers) |
| 296 body = response.read() | 323 body = response.read() |
| 297 if response.status > 300: | 324 if response.status > 300: |
| 298 raise self.provider.storage_response_error( | 325 raise self.provider.storage_response_error( |
| 299 response.status, response.reason, body) | 326 response.status, response.reason, body) |
| 300 rs = ResultSet([('Bucket', self.bucket_class)]) | 327 rs = ResultSet([('Bucket', self.bucket_class)]) |
| 301 h = handler.XmlHandler(rs, self) | 328 h = handler.XmlHandler(rs, self) |
| 302 xml.sax.parseString(body, h) | 329 xml.sax.parseString(body, h) |
| 303 return rs | 330 return rs |
| 304 | 331 |
| 305 def get_canonical_user_id(self, headers=None): | 332 def get_canonical_user_id(self, headers=None): |
| 306 """ | 333 """ |
| 307 Convenience method that returns the "CanonicalUserID" of the user who's
credentials | 334 Convenience method that returns the "CanonicalUserID" of the |
| 308 are associated with the connection. The only way to get this value is t
o do a GET | 335 user who's credentials are associated with the connection. |
| 309 request on the service which returns all buckets associated with the acc
ount. As part | 336 The only way to get this value is to do a GET request on the |
| 310 of that response, the canonical userid is returned. This method simply
does all of | 337 service which returns all buckets associated with the account. |
| 311 that and then returns just the user id. | 338 As part of that response, the canonical userid is returned. |
| 339 This method simply does all of that and then returns just the |
| 340 user id. |
| 312 | 341 |
| 313 :rtype: string | 342 :rtype: string |
| 314 :return: A string containing the canonical user id. | 343 :return: A string containing the canonical user id. |
| 315 """ | 344 """ |
| 316 rs = self.get_all_buckets(headers=headers) | 345 rs = self.get_all_buckets(headers=headers) |
| 317 return rs.ID | 346 return rs.ID |
| 318 | 347 |
| 319 def get_bucket(self, bucket_name, validate=True, headers=None): | 348 def get_bucket(self, bucket_name, validate=True, headers=None): |
| 320 bucket = self.bucket_class(self, bucket_name) | 349 bucket = self.bucket_class(self, bucket_name) |
| 321 if validate: | 350 if validate: |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 392 host = self.calling_format.build_host(self.server_name(), bucket) | 421 host = self.calling_format.build_host(self.server_name(), bucket) |
| 393 if query_args: | 422 if query_args: |
| 394 path += '?' + query_args | 423 path += '?' + query_args |
| 395 boto.log.debug('path=%s' % path) | 424 boto.log.debug('path=%s' % path) |
| 396 auth_path += '?' + query_args | 425 auth_path += '?' + query_args |
| 397 boto.log.debug('auth_path=%s' % auth_path) | 426 boto.log.debug('auth_path=%s' % auth_path) |
| 398 return AWSAuthConnection.make_request(self, method, path, headers, | 427 return AWSAuthConnection.make_request(self, method, path, headers, |
| 399 data, host, auth_path, sender, | 428 data, host, auth_path, sender, |
| 400 override_num_retries=override_num_retries) | 429 override_num_retries=override_num_retries) |
| 401 | 430 |
| OLD | NEW |