~ntt-pf-lab/nova/monkey_patch_notification

« back to all changes in this revision

Viewing changes to vendor/boto/boto/s3/connection.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
 
2
#
 
3
# Permission is hereby granted, free of charge, to any person obtaining a
 
4
# copy of this software and associated documentation files (the
 
5
# "Software"), to deal in the Software without restriction, including
 
6
# without limitation the rights to use, copy, modify, merge, publish, dis-
 
7
# tribute, sublicense, and/or sell copies of the Software, and to permit
 
8
# persons to whom the Software is furnished to do so, subject to the fol-
 
9
# lowing conditions:
 
10
#
 
11
# The above copyright notice and this permission notice shall be included
 
12
# in all copies or substantial portions of the Software.
 
13
#
 
14
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 
15
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
 
16
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
 
17
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 
18
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
19
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 
20
# IN THE SOFTWARE.
 
21
 
 
22
import xml.sax
 
23
import urllib, base64
 
24
import time
 
25
import boto.utils
 
26
from boto.connection import AWSAuthConnection
 
27
from boto import handler
 
28
from boto.s3.bucket import Bucket
 
29
from boto.s3.key import Key
 
30
from boto.resultset import ResultSet
 
31
from boto.exception import S3ResponseError, S3CreateError, BotoClientError
 
32
 
 
33
def assert_case_insensitive(f):
 
34
    def wrapper(*args, **kwargs):
 
35
        if len(args) == 3 and not (args[2].islower() or args[2].isalnum()):
 
36
            raise BotoClientError("Bucket names cannot contain upper-case " \
 
37
            "characters when using either the sub-domain or virtual " \
 
38
        "hosting calling format.")
 
39
        return f(*args, **kwargs)
 
40
    return wrapper
 
41
 
 
42
class _CallingFormat:
 
43
    def build_url_base(self, protocol, server, bucket, key=''):
 
44
        url_base = '%s://' % protocol
 
45
        url_base += self.build_host(server, bucket)
 
46
        url_base += self.build_path_base(bucket, key)
 
47
        return url_base
 
48
 
 
49
    def build_host(self, server, bucket):
 
50
        if bucket == '':
 
51
            return server
 
52
        else:
 
53
            return self.get_bucket_server(server, bucket)
 
54
 
 
55
    def build_auth_path(self, bucket, key=''):
 
56
        path = ''
 
57
        if bucket != '':
 
58
            path = '/' + bucket
 
59
        return path + '/%s' % urllib.quote(key)
 
60
 
 
61
    def build_path_base(self, bucket, key=''):
 
62
        return '/%s' % urllib.quote(key)
 
63
 
 
64
class SubdomainCallingFormat(_CallingFormat):
 
65
    @assert_case_insensitive
 
66
    def get_bucket_server(self, server, bucket):
 
67
        return '%s.%s' % (bucket, server)
 
68
 
 
69
class VHostCallingFormat(_CallingFormat):
 
70
    @assert_case_insensitive
 
71
    def get_bucket_server(self, server, bucket):
 
72
        return bucket
 
73
 
 
74
class OrdinaryCallingFormat(_CallingFormat):
 
75
    def get_bucket_server(self, server, bucket):
 
76
        return server
 
77
 
 
78
    def build_path_base(self, bucket, key=''):
 
79
        path_base = '/'
 
80
        if bucket:
 
81
            path_base += "%s/" % bucket
 
82
        return path_base + urllib.quote(key)
 
83
 
 
84
class Location:
 
85
    DEFAULT = ''
 
86
    EU = 'EU'
 
87
    USWest = 'us-west-1'
 
88
 
 
89
#boto.set_stream_logger('s3')
 
90
    
 
91
class S3Connection(AWSAuthConnection):
 
92
 
 
93
    DefaultHost = 's3.amazonaws.com'
 
94
    QueryString = 'Signature=%s&Expires=%d&AWSAccessKeyId=%s'
 
95
 
 
96
    def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
 
97
                 is_secure=True, port=None, proxy=None, proxy_port=None,
 
98
                 proxy_user=None, proxy_pass=None,
 
99
                 host=DefaultHost, debug=0, https_connection_factory=None,
 
100
                 calling_format=SubdomainCallingFormat(), path='/'):
 
101
        self.calling_format = calling_format
 
102
        AWSAuthConnection.__init__(self, host,
 
103
                aws_access_key_id, aws_secret_access_key,
 
104
                is_secure, port, proxy, proxy_port, proxy_user, proxy_pass,
 
105
                debug=debug, https_connection_factory=https_connection_factory,
 
106
                path=path)
 
107
 
 
108
    def __iter__(self):
 
109
        return self.get_all_buckets()
 
110
 
 
111
    def __contains__(self, bucket_name):
 
112
       return not (self.lookup(bucket_name) is None)
 
113
 
 
114
    def build_post_policy(self, expiration_time, conditions):
 
115
        """
 
116
        Taken from the AWS book Python examples and modified for use with boto
 
117
        """
 
118
        if type(expiration_time) != time.struct_time:
 
119
            raise 'Policy document must include a valid expiration Time object'
 
120
 
 
121
        # Convert conditions object mappings to condition statements
 
122
 
 
123
        return '{"expiration": "%s",\n"conditions": [%s]}' % \
 
124
            (time.strftime(boto.utils.ISO8601, expiration_time), ",".join(conditions))
 
125
 
 
126
 
 
127
    def build_post_form_args(self, bucket_name, key, expires_in = 6000,
 
128
                        acl = None, success_action_redirect = None, max_content_length = None,
 
129
                        http_method = "http", fields=None, conditions=None):
 
130
        """
 
131
        Taken from the AWS book Python examples and modified for use with boto
 
132
        This only returns the arguments required for the post form, not the actual form
 
133
        This does not return the file input field which also needs to be added
 
134
        
 
135
        :param bucket_name: Bucket to submit to
 
136
        :type bucket_name: string 
 
137
        
 
138
        :param key:  Key name, optionally add ${filename} to the end to attach the submitted filename
 
139
        :type key: string
 
140
        
 
141
        :param expires_in: Time (in seconds) before this expires, defaults to 6000
 
142
        :type expires_in: integer
 
143
        
 
144
        :param acl: ACL rule to use, if any
 
145
        :type acl: :class:`boto.s3.acl.ACL`
 
146
        
 
147
        :param success_action_redirect: URL to redirect to on success
 
148
        :type success_action_redirect: string 
 
149
        
 
150
        :param max_content_length: Maximum size for this file
 
151
        :type max_content_length: integer 
 
152
        
 
153
        :type http_method: string
 
154
        :param http_method:  HTTP Method to use, "http" or "https"
 
155
        
 
156
        
 
157
        :rtype: dict
 
158
        :return: A dictionary containing field names/values as well as a url to POST to
 
159
        
 
160
            .. code-block:: python
 
161
            
 
162
                {
 
163
                    "action": action_url_to_post_to, 
 
164
                    "fields": [ 
 
165
                        {
 
166
                            "name": field_name, 
 
167
                            "value":  field_value
 
168
                        }, 
 
169
                        {
 
170
                            "name": field_name2, 
 
171
                            "value": field_value2
 
172
                        } 
 
173
                    ] 
 
174
                }
 
175
            
 
176
        """
 
177
        if fields == None:
 
178
            fields = []
 
179
        if conditions == None:
 
180
            conditions = []
 
181
        expiration = time.gmtime(int(time.time() + expires_in))
 
182
 
 
183
        # Generate policy document
 
184
        conditions.append('{"bucket": "%s"}' % bucket_name)
 
185
        if key.endswith("${filename}"):
 
186
            conditions.append('["starts-with", "$key", "%s"]' % key[:-len("${filename}")])
 
187
        else:
 
188
            conditions.append('{"key": "%s"}' % key)
 
189
        if acl:
 
190
            conditions.append('{"acl": "%s"}' % acl)
 
191
            fields.append({ "name": "acl", "value": acl})
 
192
        if success_action_redirect:
 
193
            conditions.append('{"success_action_redirect": "%s"}' % success_action_redirect)
 
194
            fields.append({ "name": "success_action_redirect", "value": success_action_redirect})
 
195
        if max_content_length:
 
196
            conditions.append('["content-length-range", 0, %i]' % max_content_length)
 
197
            fields.append({"name":'content-length-range', "value": "0,%i" % max_content_length})
 
198
 
 
199
        policy = self.build_post_policy(expiration, conditions)
 
200
 
 
201
        # Add the base64-encoded policy document as the 'policy' field
 
202
        policy_b64 = base64.b64encode(policy)
 
203
        fields.append({"name": "policy", "value": policy_b64})
 
204
 
 
205
        # Add the AWS access key as the 'AWSAccessKeyId' field
 
206
        fields.append({"name": "AWSAccessKeyId", "value": self.aws_access_key_id})
 
207
 
 
208
        # Add signature for encoded policy document as the 'AWSAccessKeyId' field
 
209
        hmac_copy = self.hmac.copy()
 
210
        hmac_copy.update(policy_b64)
 
211
        signature = base64.encodestring(hmac_copy.digest()).strip()
 
212
        fields.append({"name": "signature", "value": signature})
 
213
        fields.append({"name": "key", "value": key})
 
214
 
 
215
        # HTTPS protocol will be used if the secure HTTP option is enabled.
 
216
        url = '%s://%s.s3.amazonaws.com/' % (http_method, bucket_name)
 
217
 
 
218
        return {"action": url, "fields": fields}
 
219
 
 
220
 
 
221
    def generate_url(self, expires_in, method, bucket='', key='',
 
222
                     headers=None, query_auth=True, force_http=False):
 
223
        if not headers:
 
224
            headers = {}
 
225
        expires = int(time.time() + expires_in)
 
226
        auth_path = self.calling_format.build_auth_path(bucket, key)
 
227
        canonical_str = boto.utils.canonical_string(method, auth_path,
 
228
                                                    headers, expires)
 
229
        hmac_copy = self.hmac.copy()
 
230
        hmac_copy.update(canonical_str)
 
231
        b64_hmac = base64.encodestring(hmac_copy.digest()).strip()
 
232
        encoded_canonical = urllib.quote_plus(b64_hmac)
 
233
        self.calling_format.build_path_base(bucket, key)
 
234
        if query_auth:
 
235
            query_part = '?' + self.QueryString % (encoded_canonical, expires,
 
236
                                             self.aws_access_key_id)
 
237
            if 'x-amz-security-token' in headers:
 
238
                query_part += '&x-amz-security-token=%s' % urllib.quote(headers['x-amz-security-token']);
 
239
        else:
 
240
            query_part = ''
 
241
        if force_http:
 
242
            protocol = 'http'
 
243
            port = 80
 
244
        else:
 
245
            protocol = self.protocol
 
246
            port = self.port
 
247
        return self.calling_format.build_url_base(protocol, self.server_name(port),
 
248
                                                  bucket, key) + query_part
 
249
 
 
250
    def get_all_buckets(self, headers=None):
 
251
        response = self.make_request('GET')
 
252
        body = response.read()
 
253
        if response.status > 300:
 
254
            raise S3ResponseError(response.status, response.reason, body, headers=headers)
 
255
        rs = ResultSet([('Bucket', Bucket)])
 
256
        h = handler.XmlHandler(rs, self)
 
257
        xml.sax.parseString(body, h)
 
258
        return rs
 
259
 
 
260
    def get_canonical_user_id(self, headers=None):
 
261
        """
 
262
        Convenience method that returns the "CanonicalUserID" of the user who's credentials
 
263
        are associated with the connection.  The only way to get this value is to do a GET
 
264
        request on the service which returns all buckets associated with the account.  As part
 
265
        of that response, the canonical userid is returned.  This method simply does all of
 
266
        that and then returns just the user id.
 
267
 
 
268
        :rtype: string
 
269
        :return: A string containing the canonical user id.
 
270
        """
 
271
        rs = self.get_all_buckets(headers=headers)
 
272
        return rs.ID
 
273
 
 
274
    def get_bucket(self, bucket_name, validate=True, headers=None):
 
275
        bucket = Bucket(self, bucket_name)
 
276
        if validate:
 
277
            bucket.get_all_keys(headers, maxkeys=0)
 
278
        return bucket
 
279
 
 
280
    def lookup(self, bucket_name, validate=True, headers=None):
 
281
        try:
 
282
            bucket = self.get_bucket(bucket_name, validate, headers=headers)
 
283
        except:
 
284
            bucket = None
 
285
        return bucket
 
286
 
 
287
    def create_bucket(self, bucket_name, headers=None,
 
288
                      location=Location.DEFAULT, policy=None):
 
289
        """
 
290
        Creates a new located bucket. By default it's in the USA. You can pass
 
291
        Location.EU to create an European bucket.
 
292
 
 
293
        :type bucket_name: string
 
294
        :param bucket_name: The name of the new bucket
 
295
        
 
296
        :type headers: dict
 
297
        :param headers: Additional headers to pass along with the request to AWS.
 
298
 
 
299
        :type location: :class:`boto.s3.connection.Location`
 
300
        :param location: The location of the new bucket
 
301
        
 
302
        :type policy: :class:`boto.s3.acl.CannedACLStrings`
 
303
        :param policy: A canned ACL policy that will be applied to the new key in S3.
 
304
             
 
305
        """
 
306
        # TODO: Not sure what Exception Type from boto.exception to use.
 
307
        if not bucket_name.islower():
 
308
            raise Exception("Bucket names must be lower case.")
 
309
 
 
310
        if policy:
 
311
            if headers:
 
312
                headers['x-amz-acl'] = policy
 
313
            else:
 
314
                headers = {'x-amz-acl' : policy}
 
315
        if location == Location.DEFAULT:
 
316
            data = ''
 
317
        else:
 
318
            data = '<CreateBucketConstraint><LocationConstraint>' + \
 
319
                    location + '</LocationConstraint></CreateBucketConstraint>'
 
320
        response = self.make_request('PUT', bucket_name, headers=headers,
 
321
                data=data)
 
322
        body = response.read()
 
323
        if response.status == 409:
 
324
            raise S3CreateError(response.status, response.reason, body)
 
325
        if response.status == 200:
 
326
            return Bucket(self, bucket_name)
 
327
        else:
 
328
            raise S3ResponseError(response.status, response.reason, body)
 
329
 
 
330
    def delete_bucket(self, bucket, headers=None):
 
331
        response = self.make_request('DELETE', bucket, headers=headers)
 
332
        body = response.read()
 
333
        if response.status != 204:
 
334
            raise S3ResponseError(response.status, response.reason, body)
 
335
 
 
336
    def make_request(self, method, bucket='', key='', headers=None, data='',
 
337
            query_args=None, sender=None):
 
338
        if isinstance(bucket, Bucket):
 
339
            bucket = bucket.name
 
340
        if isinstance(key, Key):
 
341
            key = key.name
 
342
        path = self.calling_format.build_path_base(bucket, key)
 
343
        auth_path = self.calling_format.build_auth_path(bucket, key)
 
344
        host = self.calling_format.build_host(self.server_name(), bucket)
 
345
        if query_args:
 
346
            path += '?' + query_args
 
347
            auth_path += '?' + query_args
 
348
        return AWSAuthConnection.make_request(self, method, path, headers,
 
349
                data, host, auth_path, sender)
 
350