1# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
2# Copyright (c) 2010, Eucalyptus Systems, Inc.
3# All rights reserved.
4#
5# Permission is hereby granted, free of charge, to any person obtaining a
6# copy of this software and associated documentation files (the
7# "Software"), to deal in the Software without restriction, including
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
10# persons to whom the Software is furnished to do so, subject to the fol-
11# lowing conditions:
12#
13# The above copyright notice and this permission notice shall be included
14# in all copies or substantial portions of the Software.
15#
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-
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,
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
22# IN THE SOFTWARE.
23
24import boto
25from boto import handler
26from boto.resultset import ResultSet
27from boto.exception import BotoClientError
28from boto.s3.acl import Policy, CannedACLStrings, Grant
29from boto.s3.key import Key
30from boto.s3.prefix import Prefix
31from boto.s3.deletemarker import DeleteMarker
32from boto.s3.multipart import MultiPartUpload
33from boto.s3.multipart import CompleteMultiPartUpload
34from boto.s3.multidelete import MultiDeleteResult
35from boto.s3.multidelete import Error
36from boto.s3.bucketlistresultset import BucketListResultSet
37from boto.s3.bucketlistresultset import VersionedBucketListResultSet
38from boto.s3.bucketlistresultset import MultiPartUploadListResultSet
39from boto.s3.lifecycle import Lifecycle
40from boto.s3.tagging import Tags
41from boto.s3.cors import CORSConfiguration
42from boto.s3.bucketlogging import BucketLogging
43from boto.s3 import website
44import boto.jsonresponse
45import boto.utils
46import xml.sax
47import xml.sax.saxutils
48import re
49import base64
50from collections import defaultdict
51from boto.compat import BytesIO, six, StringIO, urllib
52
53# as per http://goo.gl/BDuud (02/19/2011)
54
55
56class S3WebsiteEndpointTranslate(object):
57
58    trans_region = defaultdict(lambda: 's3-website-us-east-1')
59    trans_region['eu-west-1'] = 's3-website-eu-west-1'
60    trans_region['us-west-1'] = 's3-website-us-west-1'
61    trans_region['us-west-2'] = 's3-website-us-west-2'
62    trans_region['sa-east-1'] = 's3-website-sa-east-1'
63    trans_region['ap-northeast-1'] = 's3-website-ap-northeast-1'
64    trans_region['ap-southeast-1'] = 's3-website-ap-southeast-1'
65    trans_region['ap-southeast-2'] = 's3-website-ap-southeast-2'
66    trans_region['cn-north-1'] = 's3-website.cn-north-1'
67
68    @classmethod
69    def translate_region(self, reg):
70        return self.trans_region[reg]
71
72S3Permissions = ['READ', 'WRITE', 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL']
73
74
75class Bucket(object):
76
77    LoggingGroup = 'http://acs.amazonaws.com/groups/s3/LogDelivery'
78
79    BucketPaymentBody = """<?xml version="1.0" encoding="UTF-8"?>
80       <RequestPaymentConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
81         <Payer>%s</Payer>
82       </RequestPaymentConfiguration>"""
83
84    VersioningBody = """<?xml version="1.0" encoding="UTF-8"?>
85       <VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
86         <Status>%s</Status>
87         <MfaDelete>%s</MfaDelete>
88       </VersioningConfiguration>"""
89
90    VersionRE = '<Status>([A-Za-z]+)</Status>'
91    MFADeleteRE = '<MfaDelete>([A-Za-z]+)</MfaDelete>'
92
93    def __init__(self, connection=None, name=None, key_class=Key):
94        self.name = name
95        self.connection = connection
96        self.key_class = key_class
97
98    def __repr__(self):
99        return '<Bucket: %s>' % self.name
100
101    def __iter__(self):
102        return iter(BucketListResultSet(self))
103
104    def __contains__(self, key_name):
105        return not (self.get_key(key_name) is None)
106
107    def startElement(self, name, attrs, connection):
108        return None
109
110    def endElement(self, name, value, connection):
111        if name == 'Name':
112            self.name = value
113        elif name == 'CreationDate':
114            self.creation_date = value
115        else:
116            setattr(self, name, value)
117
118    def set_key_class(self, key_class):
119        """
120        Set the Key class associated with this bucket.  By default, this
121        would be the boto.s3.key.Key class but if you want to subclass that
122        for some reason this allows you to associate your new class with a
123        bucket so that when you call bucket.new_key() or when you get a listing
124        of keys in the bucket you will get an instances of your key class
125        rather than the default.
126
127        :type key_class: class
128        :param key_class: A subclass of Key that can be more specific
129        """
130        self.key_class = key_class
131
132    def lookup(self, key_name, headers=None):
133        """
134        Deprecated: Please use get_key method.
135
136        :type key_name: string
137        :param key_name: The name of the key to retrieve
138
139        :rtype: :class:`boto.s3.key.Key`
140        :returns: A Key object from this bucket.
141        """
142        return self.get_key(key_name, headers=headers)
143
144    def get_key(self, key_name, headers=None, version_id=None,
145                response_headers=None, validate=True):
146        """
147        Check to see if a particular key exists within the bucket.  This
148        method uses a HEAD request to check for the existence of the key.
149        Returns: An instance of a Key object or None
150
151        :param key_name: The name of the key to retrieve
152        :type key_name: string
153
154        :param headers: The headers to send when retrieving the key
155        :type headers: dict
156
157        :param version_id:
158        :type version_id: string
159
160        :param response_headers: A dictionary containing HTTP
161            headers/values that will override any headers associated
162            with the stored object in the response.  See
163            http://goo.gl/EWOPb for details.
164        :type response_headers: dict
165
166        :param validate: Verifies whether the key exists. If ``False``, this
167            will not hit the service, constructing an in-memory object.
168            Default is ``True``.
169        :type validate: bool
170
171        :rtype: :class:`boto.s3.key.Key`
172        :returns: A Key object from this bucket.
173        """
174        if validate is False:
175            if headers or version_id or response_headers:
176                raise BotoClientError(
177                    "When providing 'validate=False', no other params " + \
178                    "are allowed."
179                )
180
181            # This leans on the default behavior of ``new_key`` (not hitting
182            # the service). If that changes, that behavior should migrate here.
183            return self.new_key(key_name)
184
185        query_args_l = []
186        if version_id:
187            query_args_l.append('versionId=%s' % version_id)
188        if response_headers:
189            for rk, rv in six.iteritems(response_headers):
190                query_args_l.append('%s=%s' % (rk, urllib.parse.quote(rv)))
191
192        key, resp = self._get_key_internal(key_name, headers, query_args_l)
193        return key
194
195    def _get_key_internal(self, key_name, headers, query_args_l):
196        query_args = '&'.join(query_args_l) or None
197        response = self.connection.make_request('HEAD', self.name, key_name,
198                                                headers=headers,
199                                                query_args=query_args)
200        response.read()
201        # Allow any success status (2xx) - for example this lets us
202        # support Range gets, which return status 206:
203        if response.status / 100 == 2:
204            k = self.key_class(self)
205            provider = self.connection.provider
206            k.metadata = boto.utils.get_aws_metadata(response.msg, provider)
207            for field in Key.base_fields:
208                k.__dict__[field.lower().replace('-', '_')] = \
209                    response.getheader(field)
210            # the following machinations are a workaround to the fact that
211            # apache/fastcgi omits the content-length header on HEAD
212            # requests when the content-length is zero.
213            # See http://goo.gl/0Tdax for more details.
214            clen = response.getheader('content-length')
215            if clen:
216                k.size = int(response.getheader('content-length'))
217            else:
218                k.size = 0
219            k.name = key_name
220            k.handle_version_headers(response)
221            k.handle_encryption_headers(response)
222            k.handle_restore_headers(response)
223            k.handle_addl_headers(response.getheaders())
224            return k, response
225        else:
226            if response.status == 404:
227                return None, response
228            else:
229                raise self.connection.provider.storage_response_error(
230                    response.status, response.reason, '')
231
232    def list(self, prefix='', delimiter='', marker='', headers=None,
233             encoding_type=None):
234        """
235        List key objects within a bucket.  This returns an instance of an
236        BucketListResultSet that automatically handles all of the result
237        paging, etc. from S3.  You just need to keep iterating until
238        there are no more results.
239
240        Called with no arguments, this will return an iterator object across
241        all keys within the bucket.
242
243        The Key objects returned by the iterator are obtained by parsing
244        the results of a GET on the bucket, also known as the List Objects
245        request.  The XML returned by this request contains only a subset
246        of the information about each key.  Certain metadata fields such
247        as Content-Type and user metadata are not available in the XML.
248        Therefore, if you want these additional metadata fields you will
249        have to do a HEAD request on the Key in the bucket.
250
251        :type prefix: string
252        :param prefix: allows you to limit the listing to a particular
253            prefix.  For example, if you call the method with
254            prefix='/foo/' then the iterator will only cycle through
255            the keys that begin with the string '/foo/'.
256
257        :type delimiter: string
258        :param delimiter: can be used in conjunction with the prefix
259            to allow you to organize and browse your keys
260            hierarchically. See http://goo.gl/Xx63h for more details.
261
262        :type marker: string
263        :param marker: The "marker" of where you are in the result set
264
265        :param encoding_type: Requests Amazon S3 to encode the response and
266            specifies the encoding method to use.
267
268            An object key can contain any Unicode character; however, XML 1.0
269            parser cannot parse some characters, such as characters with an
270            ASCII value from 0 to 10. For characters that are not supported in
271            XML 1.0, you can add this parameter to request that Amazon S3
272            encode the keys in the response.
273
274            Valid options: ``url``
275        :type encoding_type: string
276
277        :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet`
278        :return: an instance of a BucketListResultSet that handles paging, etc
279        """
280        return BucketListResultSet(self, prefix, delimiter, marker, headers,
281                                   encoding_type=encoding_type)
282
283    def list_versions(self, prefix='', delimiter='', key_marker='',
284                      version_id_marker='', headers=None, encoding_type=None):
285        """
286        List version objects within a bucket.  This returns an
287        instance of an VersionedBucketListResultSet that automatically
288        handles all of the result paging, etc. from S3.  You just need
289        to keep iterating until there are no more results.  Called
290        with no arguments, this will return an iterator object across
291        all keys within the bucket.
292
293        :type prefix: string
294        :param prefix: allows you to limit the listing to a particular
295            prefix.  For example, if you call the method with
296            prefix='/foo/' then the iterator will only cycle through
297            the keys that begin with the string '/foo/'.
298
299        :type delimiter: string
300        :param delimiter: can be used in conjunction with the prefix
301            to allow you to organize and browse your keys
302            hierarchically. See:
303
304            http://aws.amazon.com/releasenotes/Amazon-S3/213
305
306            for more details.
307
308        :type key_marker: string
309        :param key_marker: The "marker" of where you are in the result set
310
311        :param encoding_type: Requests Amazon S3 to encode the response and
312            specifies the encoding method to use.
313
314            An object key can contain any Unicode character; however, XML 1.0
315            parser cannot parse some characters, such as characters with an
316            ASCII value from 0 to 10. For characters that are not supported in
317            XML 1.0, you can add this parameter to request that Amazon S3
318            encode the keys in the response.
319
320            Valid options: ``url``
321        :type encoding_type: string
322
323        :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet`
324        :return: an instance of a BucketListResultSet that handles paging, etc
325        """
326        return VersionedBucketListResultSet(self, prefix, delimiter,
327                                            key_marker, version_id_marker,
328                                            headers,
329                                            encoding_type=encoding_type)
330
331    def list_multipart_uploads(self, key_marker='',
332                               upload_id_marker='',
333                               headers=None, encoding_type=None):
334        """
335        List multipart upload objects within a bucket.  This returns an
336        instance of an MultiPartUploadListResultSet that automatically
337        handles all of the result paging, etc. from S3.  You just need
338        to keep iterating until there are no more results.
339
340        :type key_marker: string
341        :param key_marker: The "marker" of where you are in the result set
342
343        :type upload_id_marker: string
344        :param upload_id_marker: The upload identifier
345
346        :param encoding_type: Requests Amazon S3 to encode the response and
347            specifies the encoding method to use.
348
349            An object key can contain any Unicode character; however, XML 1.0
350            parser cannot parse some characters, such as characters with an
351            ASCII value from 0 to 10. For characters that are not supported in
352            XML 1.0, you can add this parameter to request that Amazon S3
353            encode the keys in the response.
354
355            Valid options: ``url``
356        :type encoding_type: string
357
358        :rtype: :class:`boto.s3.bucketlistresultset.BucketListResultSet`
359        :return: an instance of a BucketListResultSet that handles paging, etc
360        """
361        return MultiPartUploadListResultSet(self, key_marker,
362                                            upload_id_marker,
363                                            headers,
364                                            encoding_type=encoding_type)
365
366    def _get_all_query_args(self, params, initial_query_string=''):
367        pairs = []
368
369        if initial_query_string:
370            pairs.append(initial_query_string)
371
372        for key, value in sorted(params.items(), key=lambda x: x[0]):
373            if value is None:
374                continue
375            key = key.replace('_', '-')
376            if key == 'maxkeys':
377                key = 'max-keys'
378            if not isinstance(value, six.string_types + (six.binary_type,)):
379                value = six.text_type(value)
380            if not isinstance(value, six.binary_type):
381                value = value.encode('utf-8')
382            if value:
383                pairs.append(u'%s=%s' % (
384                    urllib.parse.quote(key),
385                    urllib.parse.quote(value)
386                ))
387
388        return '&'.join(pairs)
389
390    def _get_all(self, element_map, initial_query_string='',
391                 headers=None, **params):
392        query_args = self._get_all_query_args(
393            params,
394            initial_query_string=initial_query_string
395        )
396        response = self.connection.make_request('GET', self.name,
397                                                headers=headers,
398                                                query_args=query_args)
399        body = response.read()
400        boto.log.debug(body)
401        if response.status == 200:
402            rs = ResultSet(element_map)
403            h = handler.XmlHandler(rs, self)
404            if not isinstance(body, bytes):
405                body = body.encode('utf-8')
406            xml.sax.parseString(body, h)
407            return rs
408        else:
409            raise self.connection.provider.storage_response_error(
410                response.status, response.reason, body)
411
412    def validate_kwarg_names(self, kwargs, names):
413        """
414        Checks that all named arguments are in the specified list of names.
415
416        :type kwargs: dict
417        :param kwargs: Dictionary of kwargs to validate.
418
419        :type names: list
420        :param names: List of possible named arguments.
421        """
422        for kwarg in kwargs:
423            if kwarg not in names:
424                raise TypeError('Invalid argument "%s"!' % kwarg)
425
426    def get_all_keys(self, headers=None, **params):
427        """
428        A lower-level method for listing contents of a bucket.  This
429        closely models the actual S3 API and requires you to manually
430        handle the paging of results.  For a higher-level method that
431        handles the details of paging for you, you can use the list
432        method.
433
434        :type max_keys: int
435        :param max_keys: The maximum number of keys to retrieve
436
437        :type prefix: string
438        :param prefix: The prefix of the keys you want to retrieve
439
440        :type marker: string
441        :param marker: The "marker" of where you are in the result set
442
443        :type delimiter: string
444        :param delimiter: If this optional, Unicode string parameter
445            is included with your request, then keys that contain the
446            same string between the prefix and the first occurrence of
447            the delimiter will be rolled up into a single result
448            element in the CommonPrefixes collection. These rolled-up
449            keys are not returned elsewhere in the response.
450
451        :param encoding_type: Requests Amazon S3 to encode the response and
452            specifies the encoding method to use.
453
454            An object key can contain any Unicode character; however, XML 1.0
455            parser cannot parse some characters, such as characters with an
456            ASCII value from 0 to 10. For characters that are not supported in
457            XML 1.0, you can add this parameter to request that Amazon S3
458            encode the keys in the response.
459
460            Valid options: ``url``
461        :type encoding_type: string
462
463        :rtype: ResultSet
464        :return: The result from S3 listing the keys requested
465
466        """
467        self.validate_kwarg_names(params, ['maxkeys', 'max_keys', 'prefix',
468                                           'marker', 'delimiter',
469                                           'encoding_type'])
470        return self._get_all([('Contents', self.key_class),
471                              ('CommonPrefixes', Prefix)],
472                             '', headers, **params)
473
474    def get_all_versions(self, headers=None, **params):
475        """
476        A lower-level, version-aware method for listing contents of a
477        bucket.  This closely models the actual S3 API and requires
478        you to manually handle the paging of results.  For a
479        higher-level method that handles the details of paging for
480        you, you can use the list method.
481
482        :type max_keys: int
483        :param max_keys: The maximum number of keys to retrieve
484
485        :type prefix: string
486        :param prefix: The prefix of the keys you want to retrieve
487
488        :type key_marker: string
489        :param key_marker: The "marker" of where you are in the result set
490            with respect to keys.
491
492        :type version_id_marker: string
493        :param version_id_marker: The "marker" of where you are in the result
494            set with respect to version-id's.
495
496        :type delimiter: string
497        :param delimiter: If this optional, Unicode string parameter
498            is included with your request, then keys that contain the
499            same string between the prefix and the first occurrence of
500            the delimiter will be rolled up into a single result
501            element in the CommonPrefixes collection. These rolled-up
502            keys are not returned elsewhere in the response.
503
504        :param encoding_type: Requests Amazon S3 to encode the response and
505            specifies the encoding method to use.
506
507            An object key can contain any Unicode character; however, XML 1.0
508            parser cannot parse some characters, such as characters with an
509            ASCII value from 0 to 10. For characters that are not supported in
510            XML 1.0, you can add this parameter to request that Amazon S3
511            encode the keys in the response.
512
513            Valid options: ``url``
514        :type encoding_type: string
515
516        :rtype: ResultSet
517        :return: The result from S3 listing the keys requested
518        """
519        self.validate_get_all_versions_params(params)
520        return self._get_all([('Version', self.key_class),
521                              ('CommonPrefixes', Prefix),
522                              ('DeleteMarker', DeleteMarker)],
523                             'versions', headers, **params)
524
525    def validate_get_all_versions_params(self, params):
526        """
527        Validate that the parameters passed to get_all_versions are valid.
528        Overridden by subclasses that allow a different set of parameters.
529
530        :type params: dict
531        :param params: Parameters to validate.
532        """
533        self.validate_kwarg_names(
534                params, ['maxkeys', 'max_keys', 'prefix', 'key_marker',
535                         'version_id_marker', 'delimiter', 'encoding_type'])
536
537    def get_all_multipart_uploads(self, headers=None, **params):
538        """
539        A lower-level, version-aware method for listing active
540        MultiPart uploads for a bucket.  This closely models the
541        actual S3 API and requires you to manually handle the paging
542        of results.  For a higher-level method that handles the
543        details of paging for you, you can use the list method.
544
545        :type max_uploads: int
546        :param max_uploads: The maximum number of uploads to retrieve.
547            Default value is 1000.
548
549        :type key_marker: string
550        :param key_marker: Together with upload_id_marker, this
551            parameter specifies the multipart upload after which
552            listing should begin.  If upload_id_marker is not
553            specified, only the keys lexicographically greater than
554            the specified key_marker will be included in the list.
555
556            If upload_id_marker is specified, any multipart uploads
557            for a key equal to the key_marker might also be included,
558            provided those multipart uploads have upload IDs
559            lexicographically greater than the specified
560            upload_id_marker.
561
562        :type upload_id_marker: string
563        :param upload_id_marker: Together with key-marker, specifies
564            the multipart upload after which listing should begin. If
565            key_marker is not specified, the upload_id_marker
566            parameter is ignored.  Otherwise, any multipart uploads
567            for a key equal to the key_marker might be included in the
568            list only if they have an upload ID lexicographically
569            greater than the specified upload_id_marker.
570
571        :type encoding_type: string
572        :param encoding_type: Requests Amazon S3 to encode the response and
573            specifies the encoding method to use.
574
575            An object key can contain any Unicode character; however, XML 1.0
576            parser cannot parse some characters, such as characters with an
577            ASCII value from 0 to 10. For characters that are not supported in
578            XML 1.0, you can add this parameter to request that Amazon S3
579            encode the keys in the response.
580
581            Valid options: ``url``
582
583        :type delimiter: string
584        :param delimiter: Character you use to group keys.
585            All keys that contain the same string between the prefix, if
586            specified, and the first occurrence of the delimiter after the
587            prefix are grouped under a single result element, CommonPrefixes.
588            If you don't specify the prefix parameter, then the substring
589            starts at the beginning of the key. The keys that are grouped
590            under CommonPrefixes result element are not returned elsewhere
591            in the response.
592
593        :type prefix: string
594        :param prefix: Lists in-progress uploads only for those keys that
595            begin with the specified prefix. You can use prefixes to separate
596            a bucket into different grouping of keys. (You can think of using
597            prefix to make groups in the same way you'd use a folder in a
598            file system.)
599
600        :rtype: ResultSet
601        :return: The result from S3 listing the uploads requested
602
603        """
604        self.validate_kwarg_names(params, ['max_uploads', 'key_marker',
605                                           'upload_id_marker', 'encoding_type',
606                                           'delimiter', 'prefix'])
607        return self._get_all([('Upload', MultiPartUpload),
608                              ('CommonPrefixes', Prefix)],
609                             'uploads', headers, **params)
610
611    def new_key(self, key_name=None):
612        """
613        Creates a new key
614
615        :type key_name: string
616        :param key_name: The name of the key to create
617
618        :rtype: :class:`boto.s3.key.Key` or subclass
619        :returns: An instance of the newly created key object
620        """
621        if not key_name:
622            raise ValueError('Empty key names are not allowed')
623        return self.key_class(self, key_name)
624
625    def generate_url(self, expires_in, method='GET', headers=None,
626                     force_http=False, response_headers=None,
627                     expires_in_absolute=False):
628        return self.connection.generate_url(expires_in, method, self.name,
629                                            headers=headers,
630                                            force_http=force_http,
631                                            response_headers=response_headers,
632                                            expires_in_absolute=expires_in_absolute)
633
634    def delete_keys(self, keys, quiet=False, mfa_token=None, headers=None):
635        """
636        Deletes a set of keys using S3's Multi-object delete API. If a
637        VersionID is specified for that key then that version is removed.
638        Returns a MultiDeleteResult Object, which contains Deleted
639        and Error elements for each key you ask to delete.
640
641        :type keys: list
642        :param keys: A list of either key_names or (key_name, versionid) pairs
643            or a list of Key instances.
644
645        :type quiet: boolean
646        :param quiet: In quiet mode the response includes only keys
647            where the delete operation encountered an error. For a
648            successful deletion, the operation does not return any
649            information about the delete in the response body.
650
651        :type mfa_token: tuple or list of strings
652        :param mfa_token: A tuple or list consisting of the serial
653            number from the MFA device and the current value of the
654            six-digit token associated with the device.  This value is
655            required anytime you are deleting versioned objects from a
656            bucket that has the MFADelete option on the bucket.
657
658        :returns: An instance of MultiDeleteResult
659        """
660        ikeys = iter(keys)
661        result = MultiDeleteResult(self)
662        provider = self.connection.provider
663        query_args = 'delete'
664
665        def delete_keys2(hdrs):
666            hdrs = hdrs or {}
667            data = u"""<?xml version="1.0" encoding="UTF-8"?>"""
668            data += u"<Delete>"
669            if quiet:
670                data += u"<Quiet>true</Quiet>"
671            count = 0
672            while count < 1000:
673                try:
674                    key = next(ikeys)
675                except StopIteration:
676                    break
677                if isinstance(key, six.string_types):
678                    key_name = key
679                    version_id = None
680                elif isinstance(key, tuple) and len(key) == 2:
681                    key_name, version_id = key
682                elif (isinstance(key, Key) or isinstance(key, DeleteMarker)) and key.name:
683                    key_name = key.name
684                    version_id = key.version_id
685                else:
686                    if isinstance(key, Prefix):
687                        key_name = key.name
688                        code = 'PrefixSkipped'   # Don't delete Prefix
689                    else:
690                        key_name = repr(key)   # try get a string
691                        code = 'InvalidArgument'  # other unknown type
692                    message = 'Invalid. No delete action taken for this object.'
693                    error = Error(key_name, code=code, message=message)
694                    result.errors.append(error)
695                    continue
696                count += 1
697                data += u"<Object><Key>%s</Key>" % xml.sax.saxutils.escape(key_name)
698                if version_id:
699                    data += u"<VersionId>%s</VersionId>" % version_id
700                data += u"</Object>"
701            data += u"</Delete>"
702            if count <= 0:
703                return False  # no more
704            data = data.encode('utf-8')
705            fp = BytesIO(data)
706            md5 = boto.utils.compute_md5(fp)
707            hdrs['Content-MD5'] = md5[1]
708            hdrs['Content-Type'] = 'text/xml'
709            if mfa_token:
710                hdrs[provider.mfa_header] = ' '.join(mfa_token)
711            response = self.connection.make_request('POST', self.name,
712                                                    headers=hdrs,
713                                                    query_args=query_args,
714                                                    data=data)
715            body = response.read()
716            if response.status == 200:
717                h = handler.XmlHandler(result, self)
718                if not isinstance(body, bytes):
719                    body = body.encode('utf-8')
720                xml.sax.parseString(body, h)
721                return count >= 1000  # more?
722            else:
723                raise provider.storage_response_error(response.status,
724                                                      response.reason,
725                                                      body)
726        while delete_keys2(headers):
727            pass
728        return result
729
730    def delete_key(self, key_name, headers=None, version_id=None,
731                   mfa_token=None):
732        """
733        Deletes a key from the bucket.  If a version_id is provided,
734        only that version of the key will be deleted.
735
736        :type key_name: string
737        :param key_name: The key name to delete
738
739        :type version_id: string
740        :param version_id: The version ID (optional)
741
742        :type mfa_token: tuple or list of strings
743        :param mfa_token: A tuple or list consisting of the serial
744            number from the MFA device and the current value of the
745            six-digit token associated with the device.  This value is
746            required anytime you are deleting versioned objects from a
747            bucket that has the MFADelete option on the bucket.
748
749        :rtype: :class:`boto.s3.key.Key` or subclass
750        :returns: A key object holding information on what was
751            deleted.  The Caller can see if a delete_marker was
752            created or removed and what version_id the delete created
753            or removed.
754        """
755        if not key_name:
756            raise ValueError('Empty key names are not allowed')
757        return self._delete_key_internal(key_name, headers=headers,
758                                         version_id=version_id,
759                                         mfa_token=mfa_token,
760                                         query_args_l=None)
761
762    def _delete_key_internal(self, key_name, headers=None, version_id=None,
763                             mfa_token=None, query_args_l=None):
764        query_args_l = query_args_l or []
765        provider = self.connection.provider
766        if version_id:
767            query_args_l.append('versionId=%s' % version_id)
768        query_args = '&'.join(query_args_l) or None
769        if mfa_token:
770            if not headers:
771                headers = {}
772            headers[provider.mfa_header] = ' '.join(mfa_token)
773        response = self.connection.make_request('DELETE', self.name, key_name,
774                                                headers=headers,
775                                                query_args=query_args)
776        body = response.read()
777        if response.status != 204:
778            raise provider.storage_response_error(response.status,
779                                                  response.reason, body)
780        else:
781            # return a key object with information on what was deleted.
782            k = self.key_class(self)
783            k.name = key_name
784            k.handle_version_headers(response)
785            k.handle_addl_headers(response.getheaders())
786            return k
787
788    def copy_key(self, new_key_name, src_bucket_name,
789                 src_key_name, metadata=None, src_version_id=None,
790                 storage_class='STANDARD', preserve_acl=False,
791                 encrypt_key=False, headers=None, query_args=None):
792        """
793        Create a new key in the bucket by copying another existing key.
794
795        :type new_key_name: string
796        :param new_key_name: The name of the new key
797
798        :type src_bucket_name: string
799        :param src_bucket_name: The name of the source bucket
800
801        :type src_key_name: string
802        :param src_key_name: The name of the source key
803
804        :type src_version_id: string
805        :param src_version_id: The version id for the key.  This param
806            is optional.  If not specified, the newest version of the
807            key will be copied.
808
809        :type metadata: dict
810        :param metadata: Metadata to be associated with new key.  If
811            metadata is supplied, it will replace the metadata of the
812            source key being copied.  If no metadata is supplied, the
813            source key's metadata will be copied to the new key.
814
815        :type storage_class: string
816        :param storage_class: The storage class of the new key.  By
817            default, the new key will use the standard storage class.
818            Possible values are: STANDARD | REDUCED_REDUNDANCY
819
820        :type preserve_acl: bool
821        :param preserve_acl: If True, the ACL from the source key will
822            be copied to the destination key.  If False, the
823            destination key will have the default ACL.  Note that
824            preserving the ACL in the new key object will require two
825            additional API calls to S3, one to retrieve the current
826            ACL and one to set that ACL on the new object.  If you
827            don't care about the ACL, a value of False will be
828            significantly more efficient.
829
830        :type encrypt_key: bool
831        :param encrypt_key: If True, the new copy of the object will
832            be encrypted on the server-side by S3 and will be stored
833            in an encrypted form while at rest in S3.
834
835        :type headers: dict
836        :param headers: A dictionary of header name/value pairs.
837
838        :type query_args: string
839        :param query_args: A string of additional querystring arguments
840            to append to the request
841
842        :rtype: :class:`boto.s3.key.Key` or subclass
843        :returns: An instance of the newly created key object
844        """
845        headers = headers or {}
846        provider = self.connection.provider
847        src_key_name = boto.utils.get_utf8_value(src_key_name)
848        if preserve_acl:
849            if self.name == src_bucket_name:
850                src_bucket = self
851            else:
852                src_bucket = self.connection.get_bucket(
853                    src_bucket_name, validate=False)
854            acl = src_bucket.get_xml_acl(src_key_name)
855        if encrypt_key:
856            headers[provider.server_side_encryption_header] = 'AES256'
857        src = '%s/%s' % (src_bucket_name, urllib.parse.quote(src_key_name))
858        if src_version_id:
859            src += '?versionId=%s' % src_version_id
860        headers[provider.copy_source_header] = str(src)
861        # make sure storage_class_header key exists before accessing it
862        if provider.storage_class_header and storage_class:
863            headers[provider.storage_class_header] = storage_class
864        if metadata is not None:
865            headers[provider.metadata_directive_header] = 'REPLACE'
866            headers = boto.utils.merge_meta(headers, metadata, provider)
867        elif not query_args:  # Can't use this header with multi-part copy.
868            headers[provider.metadata_directive_header] = 'COPY'
869        response = self.connection.make_request('PUT', self.name, new_key_name,
870                                                headers=headers,
871                                                query_args=query_args)
872        body = response.read()
873        if response.status == 200:
874            key = self.new_key(new_key_name)
875            h = handler.XmlHandler(key, self)
876            if not isinstance(body, bytes):
877                body = body.encode('utf-8')
878            xml.sax.parseString(body, h)
879            if hasattr(key, 'Error'):
880                raise provider.storage_copy_error(key.Code, key.Message, body)
881            key.handle_version_headers(response)
882            key.handle_addl_headers(response.getheaders())
883            if preserve_acl:
884                self.set_xml_acl(acl, new_key_name)
885            return key
886        else:
887            raise provider.storage_response_error(response.status,
888                                                  response.reason, body)
889
890    def set_canned_acl(self, acl_str, key_name='', headers=None,
891                       version_id=None):
892        assert acl_str in CannedACLStrings
893
894        if headers:
895            headers[self.connection.provider.acl_header] = acl_str
896        else:
897            headers = {self.connection.provider.acl_header: acl_str}
898
899        query_args = 'acl'
900        if version_id:
901            query_args += '&versionId=%s' % version_id
902        response = self.connection.make_request('PUT', self.name, key_name,
903                headers=headers, query_args=query_args)
904        body = response.read()
905        if response.status != 200:
906            raise self.connection.provider.storage_response_error(
907                response.status, response.reason, body)
908
909    def get_xml_acl(self, key_name='', headers=None, version_id=None):
910        query_args = 'acl'
911        if version_id:
912            query_args += '&versionId=%s' % version_id
913        response = self.connection.make_request('GET', self.name, key_name,
914                                                query_args=query_args,
915                                                headers=headers)
916        body = response.read()
917        if response.status != 200:
918            raise self.connection.provider.storage_response_error(
919                response.status, response.reason, body)
920        return body
921
922    def set_xml_acl(self, acl_str, key_name='', headers=None, version_id=None,
923                    query_args='acl'):
924        if version_id:
925            query_args += '&versionId=%s' % version_id
926        if not isinstance(acl_str, bytes):
927            acl_str = acl_str.encode('utf-8')
928        response = self.connection.make_request('PUT', self.name, key_name,
929                                                data=acl_str,
930                                                query_args=query_args,
931                                                headers=headers)
932        body = response.read()
933        if response.status != 200:
934            raise self.connection.provider.storage_response_error(
935                response.status, response.reason, body)
936
937    def set_acl(self, acl_or_str, key_name='', headers=None, version_id=None):
938        if isinstance(acl_or_str, Policy):
939            self.set_xml_acl(acl_or_str.to_xml(), key_name,
940                             headers, version_id)
941        else:
942            self.set_canned_acl(acl_or_str, key_name,
943                                headers, version_id)
944
945    def get_acl(self, key_name='', headers=None, version_id=None):
946        query_args = 'acl'
947        if version_id:
948            query_args += '&versionId=%s' % version_id
949        response = self.connection.make_request('GET', self.name, key_name,
950                                                query_args=query_args,
951                                                headers=headers)
952        body = response.read()
953        if response.status == 200:
954            policy = Policy(self)
955            h = handler.XmlHandler(policy, self)
956            if not isinstance(body, bytes):
957                body = body.encode('utf-8')
958            xml.sax.parseString(body, h)
959            return policy
960        else:
961            raise self.connection.provider.storage_response_error(
962                response.status, response.reason, body)
963
964    def set_subresource(self, subresource, value, key_name='', headers=None,
965                        version_id=None):
966        """
967        Set a subresource for a bucket or key.
968
969        :type subresource: string
970        :param subresource: The subresource to set.
971
972        :type value: string
973        :param value: The value of the subresource.
974
975        :type key_name: string
976        :param key_name: The key to operate on, or None to operate on the
977            bucket.
978
979        :type headers: dict
980        :param headers: Additional HTTP headers to include in the request.
981
982        :type src_version_id: string
983        :param src_version_id: Optional. The version id of the key to
984            operate on. If not specified, operate on the newest
985            version.
986        """
987        if not subresource:
988            raise TypeError('set_subresource called with subresource=None')
989        query_args = subresource
990        if version_id:
991            query_args += '&versionId=%s' % version_id
992        if not isinstance(value, bytes):
993            value = value.encode('utf-8')
994        response = self.connection.make_request('PUT', self.name, key_name,
995                                                data=value,
996                                                query_args=query_args,
997                                                headers=headers)
998        body = response.read()
999        if response.status != 200:
1000            raise self.connection.provider.storage_response_error(
1001                response.status, response.reason, body)
1002
1003    def get_subresource(self, subresource, key_name='', headers=None,
1004                        version_id=None):
1005        """
1006        Get a subresource for a bucket or key.
1007
1008        :type subresource: string
1009        :param subresource: The subresource to get.
1010
1011        :type key_name: string
1012        :param key_name: The key to operate on, or None to operate on the
1013            bucket.
1014
1015        :type headers: dict
1016        :param headers: Additional HTTP headers to include in the request.
1017
1018        :type src_version_id: string
1019        :param src_version_id: Optional. The version id of the key to
1020            operate on. If not specified, operate on the newest
1021            version.
1022
1023        :rtype: string
1024        :returns: The value of the subresource.
1025        """
1026        if not subresource:
1027            raise TypeError('get_subresource called with subresource=None')
1028        query_args = subresource
1029        if version_id:
1030            query_args += '&versionId=%s' % version_id
1031        response = self.connection.make_request('GET', self.name, key_name,
1032                                                query_args=query_args,
1033                                                headers=headers)
1034        body = response.read()
1035        if response.status != 200:
1036            raise self.connection.provider.storage_response_error(
1037                response.status, response.reason, body)
1038        return body
1039
1040    def make_public(self, recursive=False, headers=None):
1041        self.set_canned_acl('public-read', headers=headers)
1042        if recursive:
1043            for key in self:
1044                self.set_canned_acl('public-read', key.name, headers=headers)
1045
1046    def add_email_grant(self, permission, email_address,
1047                        recursive=False, headers=None):
1048        """
1049        Convenience method that provides a quick way to add an email grant
1050        to a bucket. This method retrieves the current ACL, creates a new
1051        grant based on the parameters passed in, adds that grant to the ACL
1052        and then PUT's the new ACL back to S3.
1053
1054        :type permission: string
1055        :param permission: The permission being granted. Should be one of:
1056            (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL).
1057
1058        :type email_address: string
1059        :param email_address: The email address associated with the AWS
1060            account your are granting the permission to.
1061
1062        :type recursive: boolean
1063        :param recursive: A boolean value to controls whether the
1064            command will apply the grant to all keys within the bucket
1065            or not.  The default value is False.  By passing a True
1066            value, the call will iterate through all keys in the
1067            bucket and apply the same grant to each key.  CAUTION: If
1068            you have a lot of keys, this could take a long time!
1069        """
1070        if permission not in S3Permissions:
1071            raise self.connection.provider.storage_permissions_error(
1072                'Unknown Permission: %s' % permission)
1073        policy = self.get_acl(headers=headers)
1074        policy.acl.add_email_grant(permission, email_address)
1075        self.set_acl(policy, headers=headers)
1076        if recursive:
1077            for key in self:
1078                key.add_email_grant(permission, email_address, headers=headers)
1079
1080    def add_user_grant(self, permission, user_id, recursive=False,
1081                       headers=None, display_name=None):
1082        """
1083        Convenience method that provides a quick way to add a canonical
1084        user grant to a bucket.  This method retrieves the current ACL,
1085        creates a new grant based on the parameters passed in, adds that
1086        grant to the ACL and then PUT's the new ACL back to S3.
1087
1088        :type permission: string
1089        :param permission: The permission being granted. Should be one of:
1090            (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL).
1091
1092        :type user_id: string
1093        :param user_id:     The canonical user id associated with the AWS
1094            account your are granting the permission to.
1095
1096        :type recursive: boolean
1097        :param recursive: A boolean value to controls whether the
1098            command will apply the grant to all keys within the bucket
1099            or not.  The default value is False.  By passing a True
1100            value, the call will iterate through all keys in the
1101            bucket and apply the same grant to each key.  CAUTION: If
1102            you have a lot of keys, this could take a long time!
1103
1104        :type display_name: string
1105        :param display_name: An option string containing the user's
1106            Display Name.  Only required on Walrus.
1107        """
1108        if permission not in S3Permissions:
1109            raise self.connection.provider.storage_permissions_error(
1110                'Unknown Permission: %s' % permission)
1111        policy = self.get_acl(headers=headers)
1112        policy.acl.add_user_grant(permission, user_id,
1113                                  display_name=display_name)
1114        self.set_acl(policy, headers=headers)
1115        if recursive:
1116            for key in self:
1117                key.add_user_grant(permission, user_id, headers=headers,
1118                                   display_name=display_name)
1119
1120    def list_grants(self, headers=None):
1121        policy = self.get_acl(headers=headers)
1122        return policy.acl.grants
1123
1124    def get_location(self):
1125        """
1126        Returns the LocationConstraint for the bucket.
1127
1128        :rtype: str
1129        :return: The LocationConstraint for the bucket or the empty
1130            string if no constraint was specified when bucket was created.
1131        """
1132        response = self.connection.make_request('GET', self.name,
1133                                                query_args='location')
1134        body = response.read()
1135        if response.status == 200:
1136            rs = ResultSet(self)
1137            h = handler.XmlHandler(rs, self)
1138            if not isinstance(body, bytes):
1139                body = body.encode('utf-8')
1140            xml.sax.parseString(body, h)
1141            return rs.LocationConstraint
1142        else:
1143            raise self.connection.provider.storage_response_error(
1144                response.status, response.reason, body)
1145
1146    def set_xml_logging(self, logging_str, headers=None):
1147        """
1148        Set logging on a bucket directly to the given xml string.
1149
1150        :type logging_str: unicode string
1151        :param logging_str: The XML for the bucketloggingstatus which
1152            will be set.  The string will be converted to utf-8 before
1153            it is sent.  Usually, you will obtain this XML from the
1154            BucketLogging object.
1155
1156        :rtype: bool
1157        :return: True if ok or raises an exception.
1158        """
1159        body = logging_str
1160        if not isinstance(body, bytes):
1161            body = body.encode('utf-8')
1162        response = self.connection.make_request('PUT', self.name, data=body,
1163                query_args='logging', headers=headers)
1164        body = response.read()
1165        if response.status == 200:
1166            return True
1167        else:
1168            raise self.connection.provider.storage_response_error(
1169                response.status, response.reason, body)
1170
1171    def enable_logging(self, target_bucket, target_prefix='',
1172                       grants=None, headers=None):
1173        """
1174        Enable logging on a bucket.
1175
1176        :type target_bucket: bucket or string
1177        :param target_bucket: The bucket to log to.
1178
1179        :type target_prefix: string
1180        :param target_prefix: The prefix which should be prepended to the
1181            generated log files written to the target_bucket.
1182
1183        :type grants: list of Grant objects
1184        :param grants: A list of extra permissions which will be granted on
1185            the log files which are created.
1186
1187        :rtype: bool
1188        :return: True if ok or raises an exception.
1189        """
1190        if isinstance(target_bucket, Bucket):
1191            target_bucket = target_bucket.name
1192        blogging = BucketLogging(target=target_bucket, prefix=target_prefix,
1193                                 grants=grants)
1194        return self.set_xml_logging(blogging.to_xml(), headers=headers)
1195
1196    def disable_logging(self, headers=None):
1197        """
1198        Disable logging on a bucket.
1199
1200        :rtype: bool
1201        :return: True if ok or raises an exception.
1202        """
1203        blogging = BucketLogging()
1204        return self.set_xml_logging(blogging.to_xml(), headers=headers)
1205
1206    def get_logging_status(self, headers=None):
1207        """
1208        Get the logging status for this bucket.
1209
1210        :rtype: :class:`boto.s3.bucketlogging.BucketLogging`
1211        :return: A BucketLogging object for this bucket.
1212        """
1213        response = self.connection.make_request('GET', self.name,
1214                query_args='logging', headers=headers)
1215        body = response.read()
1216        if response.status == 200:
1217            blogging = BucketLogging()
1218            h = handler.XmlHandler(blogging, self)
1219            if not isinstance(body, bytes):
1220                body = body.encode('utf-8')
1221            xml.sax.parseString(body, h)
1222            return blogging
1223        else:
1224            raise self.connection.provider.storage_response_error(
1225                response.status, response.reason, body)
1226
1227    def set_as_logging_target(self, headers=None):
1228        """
1229        Setup the current bucket as a logging target by granting the necessary
1230        permissions to the LogDelivery group to write log files to this bucket.
1231        """
1232        policy = self.get_acl(headers=headers)
1233        g1 = Grant(permission='WRITE', type='Group', uri=self.LoggingGroup)
1234        g2 = Grant(permission='READ_ACP', type='Group', uri=self.LoggingGroup)
1235        policy.acl.add_grant(g1)
1236        policy.acl.add_grant(g2)
1237        self.set_acl(policy, headers=headers)
1238
1239    def get_request_payment(self, headers=None):
1240        response = self.connection.make_request('GET', self.name,
1241                query_args='requestPayment', headers=headers)
1242        body = response.read()
1243        if response.status == 200:
1244            return body
1245        else:
1246            raise self.connection.provider.storage_response_error(
1247                response.status, response.reason, body)
1248
1249    def set_request_payment(self, payer='BucketOwner', headers=None):
1250        body = self.BucketPaymentBody % payer
1251        response = self.connection.make_request('PUT', self.name, data=body,
1252                query_args='requestPayment', headers=headers)
1253        body = response.read()
1254        if response.status == 200:
1255            return True
1256        else:
1257            raise self.connection.provider.storage_response_error(
1258                response.status, response.reason, body)
1259
1260    def configure_versioning(self, versioning, mfa_delete=False,
1261                             mfa_token=None, headers=None):
1262        """
1263        Configure versioning for this bucket.
1264
1265        ..note:: This feature is currently in beta.
1266
1267        :type versioning: bool
1268        :param versioning: A boolean indicating whether version is
1269            enabled (True) or disabled (False).
1270
1271        :type mfa_delete: bool
1272        :param mfa_delete: A boolean indicating whether the
1273            Multi-Factor Authentication Delete feature is enabled
1274            (True) or disabled (False).  If mfa_delete is enabled then
1275            all Delete operations will require the token from your MFA
1276            device to be passed in the request.
1277
1278        :type mfa_token: tuple or list of strings
1279        :param mfa_token: A tuple or list consisting of the serial
1280            number from the MFA device and the current value of the
1281            six-digit token associated with the device.  This value is
1282            required when you are changing the status of the MfaDelete
1283            property of the bucket.
1284        """
1285        if versioning:
1286            ver = 'Enabled'
1287        else:
1288            ver = 'Suspended'
1289        if mfa_delete:
1290            mfa = 'Enabled'
1291        else:
1292            mfa = 'Disabled'
1293        body = self.VersioningBody % (ver, mfa)
1294        if mfa_token:
1295            if not headers:
1296                headers = {}
1297            provider = self.connection.provider
1298            headers[provider.mfa_header] = ' '.join(mfa_token)
1299        response = self.connection.make_request('PUT', self.name, data=body,
1300                query_args='versioning', headers=headers)
1301        body = response.read()
1302        if response.status == 200:
1303            return True
1304        else:
1305            raise self.connection.provider.storage_response_error(
1306                response.status, response.reason, body)
1307
1308    def get_versioning_status(self, headers=None):
1309        """
1310        Returns the current status of versioning on the bucket.
1311
1312        :rtype: dict
1313        :returns: A dictionary containing a key named 'Versioning'
1314            that can have a value of either Enabled, Disabled, or
1315            Suspended. Also, if MFADelete has ever been enabled on the
1316            bucket, the dictionary will contain a key named
1317            'MFADelete' which will have a value of either Enabled or
1318            Suspended.
1319        """
1320        response = self.connection.make_request('GET', self.name,
1321                query_args='versioning', headers=headers)
1322        body = response.read()
1323        if not isinstance(body, six.string_types):
1324            body = body.decode('utf-8')
1325        boto.log.debug(body)
1326        if response.status == 200:
1327            d = {}
1328            ver = re.search(self.VersionRE, body)
1329            if ver:
1330                d['Versioning'] = ver.group(1)
1331            mfa = re.search(self.MFADeleteRE, body)
1332            if mfa:
1333                d['MfaDelete'] = mfa.group(1)
1334            return d
1335        else:
1336            raise self.connection.provider.storage_response_error(
1337                response.status, response.reason, body)
1338
1339    def configure_lifecycle(self, lifecycle_config, headers=None):
1340        """
1341        Configure lifecycle for this bucket.
1342
1343        :type lifecycle_config: :class:`boto.s3.lifecycle.Lifecycle`
1344        :param lifecycle_config: The lifecycle configuration you want
1345            to configure for this bucket.
1346        """
1347        xml = lifecycle_config.to_xml()
1348        #xml = xml.encode('utf-8')
1349        fp = StringIO(xml)
1350        md5 = boto.utils.compute_md5(fp)
1351        if headers is None:
1352            headers = {}
1353        headers['Content-MD5'] = md5[1]
1354        headers['Content-Type'] = 'text/xml'
1355        response = self.connection.make_request('PUT', self.name,
1356                                                data=fp.getvalue(),
1357                                                query_args='lifecycle',
1358                                                headers=headers)
1359        body = response.read()
1360        if response.status == 200:
1361            return True
1362        else:
1363            raise self.connection.provider.storage_response_error(
1364                response.status, response.reason, body)
1365
1366    def get_lifecycle_config(self, headers=None):
1367        """
1368        Returns the current lifecycle configuration on the bucket.
1369
1370        :rtype: :class:`boto.s3.lifecycle.Lifecycle`
1371        :returns: A LifecycleConfig object that describes all current
1372            lifecycle rules in effect for the bucket.
1373        """
1374        response = self.connection.make_request('GET', self.name,
1375                query_args='lifecycle', headers=headers)
1376        body = response.read()
1377        boto.log.debug(body)
1378        if response.status == 200:
1379            lifecycle = Lifecycle()
1380            h = handler.XmlHandler(lifecycle, self)
1381            if not isinstance(body, bytes):
1382                body = body.encode('utf-8')
1383            xml.sax.parseString(body, h)
1384            return lifecycle
1385        else:
1386            raise self.connection.provider.storage_response_error(
1387                response.status, response.reason, body)
1388
1389    def delete_lifecycle_configuration(self, headers=None):
1390        """
1391        Removes all lifecycle configuration from the bucket.
1392        """
1393        response = self.connection.make_request('DELETE', self.name,
1394                                                query_args='lifecycle',
1395                                                headers=headers)
1396        body = response.read()
1397        boto.log.debug(body)
1398        if response.status == 204:
1399            return True
1400        else:
1401            raise self.connection.provider.storage_response_error(
1402                response.status, response.reason, body)
1403
1404    def configure_website(self, suffix=None, error_key=None,
1405                          redirect_all_requests_to=None,
1406                          routing_rules=None,
1407                          headers=None):
1408        """
1409        Configure this bucket to act as a website
1410
1411        :type suffix: str
1412        :param suffix: Suffix that is appended to a request that is for a
1413            "directory" on the website endpoint (e.g. if the suffix is
1414            index.html and you make a request to samplebucket/images/
1415            the data that is returned will be for the object with the
1416            key name images/index.html).  The suffix must not be empty
1417            and must not include a slash character.
1418
1419        :type error_key: str
1420        :param error_key: The object key name to use when a 4XX class
1421            error occurs.  This is optional.
1422
1423        :type redirect_all_requests_to: :class:`boto.s3.website.RedirectLocation`
1424        :param redirect_all_requests_to: Describes the redirect behavior for
1425            every request to this bucket's website endpoint. If this value is
1426            non None, no other values are considered when configuring the
1427            website configuration for the bucket. This is an instance of
1428            ``RedirectLocation``.
1429
1430        :type routing_rules: :class:`boto.s3.website.RoutingRules`
1431        :param routing_rules: Object which specifies conditions
1432            and redirects that apply when the conditions are met.
1433
1434        """
1435        config = website.WebsiteConfiguration(
1436                suffix, error_key, redirect_all_requests_to,
1437                routing_rules)
1438        return self.set_website_configuration(config, headers=headers)
1439
1440    def set_website_configuration(self, config, headers=None):
1441        """
1442        :type config: boto.s3.website.WebsiteConfiguration
1443        :param config: Configuration data
1444        """
1445        return self.set_website_configuration_xml(config.to_xml(),
1446          headers=headers)
1447
1448
1449    def set_website_configuration_xml(self, xml, headers=None):
1450        """Upload xml website configuration"""
1451        response = self.connection.make_request('PUT', self.name, data=xml,
1452                                                query_args='website',
1453                                                headers=headers)
1454        body = response.read()
1455        if response.status == 200:
1456            return True
1457        else:
1458            raise self.connection.provider.storage_response_error(
1459                response.status, response.reason, body)
1460
1461    def get_website_configuration(self, headers=None):
1462        """
1463        Returns the current status of website configuration on the bucket.
1464
1465        :rtype: dict
1466        :returns: A dictionary containing a Python representation
1467            of the XML response from S3. The overall structure is:
1468
1469        * WebsiteConfiguration
1470
1471          * IndexDocument
1472
1473            * Suffix : suffix that is appended to request that
1474              is for a "directory" on the website endpoint
1475            * ErrorDocument
1476
1477              * Key : name of object to serve when an error occurs
1478
1479        """
1480        return self.get_website_configuration_with_xml(headers)[0]
1481
1482    def get_website_configuration_obj(self, headers=None):
1483        """Get the website configuration as a
1484        :class:`boto.s3.website.WebsiteConfiguration` object.
1485        """
1486        config_xml = self.get_website_configuration_xml(headers=headers)
1487        config = website.WebsiteConfiguration()
1488        h = handler.XmlHandler(config, self)
1489        xml.sax.parseString(config_xml, h)
1490        return config
1491
1492    def get_website_configuration_with_xml(self, headers=None):
1493        """
1494        Returns the current status of website configuration on the bucket as
1495        unparsed XML.
1496
1497        :rtype: 2-Tuple
1498        :returns: 2-tuple containing:
1499
1500            1) A dictionary containing a Python representation \
1501                of the XML response. The overall structure is:
1502
1503              * WebsiteConfiguration
1504
1505                * IndexDocument
1506
1507                  * Suffix : suffix that is appended to request that \
1508                    is for a "directory" on the website endpoint
1509
1510                  * ErrorDocument
1511
1512                    * Key : name of object to serve when an error occurs
1513
1514
1515            2) unparsed XML describing the bucket's website configuration
1516
1517        """
1518
1519        body = self.get_website_configuration_xml(headers=headers)
1520        e = boto.jsonresponse.Element()
1521        h = boto.jsonresponse.XmlHandler(e, None)
1522        h.parse(body)
1523        return e, body
1524
1525    def get_website_configuration_xml(self, headers=None):
1526        """Get raw website configuration xml"""
1527        response = self.connection.make_request('GET', self.name,
1528                query_args='website', headers=headers)
1529        body = response.read().decode('utf-8')
1530        boto.log.debug(body)
1531
1532        if response.status != 200:
1533            raise self.connection.provider.storage_response_error(
1534                response.status, response.reason, body)
1535        return body
1536
1537    def delete_website_configuration(self, headers=None):
1538        """
1539        Removes all website configuration from the bucket.
1540        """
1541        response = self.connection.make_request('DELETE', self.name,
1542                query_args='website', headers=headers)
1543        body = response.read()
1544        boto.log.debug(body)
1545        if response.status == 204:
1546            return True
1547        else:
1548            raise self.connection.provider.storage_response_error(
1549                response.status, response.reason, body)
1550
1551    def get_website_endpoint(self):
1552        """
1553        Returns the fully qualified hostname to use is you want to access this
1554        bucket as a website.  This doesn't validate whether the bucket has
1555        been correctly configured as a website or not.
1556        """
1557        l = [self.name]
1558        l.append(S3WebsiteEndpointTranslate.translate_region(self.get_location()))
1559        l.append('.'.join(self.connection.host.split('.')[-2:]))
1560        return '.'.join(l)
1561
1562    def get_policy(self, headers=None):
1563        """
1564        Returns the JSON policy associated with the bucket.  The policy
1565        is returned as an uninterpreted JSON string.
1566        """
1567        response = self.connection.make_request('GET', self.name,
1568                query_args='policy', headers=headers)
1569        body = response.read()
1570        if response.status == 200:
1571            return body
1572        else:
1573            raise self.connection.provider.storage_response_error(
1574                response.status, response.reason, body)
1575
1576    def set_policy(self, policy, headers=None):
1577        """
1578        Add or replace the JSON policy associated with the bucket.
1579
1580        :type policy: str
1581        :param policy: The JSON policy as a string.
1582        """
1583        response = self.connection.make_request('PUT', self.name,
1584                                                data=policy,
1585                                                query_args='policy',
1586                                                headers=headers)
1587        body = response.read()
1588        if response.status >= 200 and response.status <= 204:
1589            return True
1590        else:
1591            raise self.connection.provider.storage_response_error(
1592                response.status, response.reason, body)
1593
1594    def delete_policy(self, headers=None):
1595        response = self.connection.make_request('DELETE', self.name,
1596                                                data='/?policy',
1597                                                query_args='policy',
1598                                                headers=headers)
1599        body = response.read()
1600        if response.status >= 200 and response.status <= 204:
1601            return True
1602        else:
1603            raise self.connection.provider.storage_response_error(
1604                response.status, response.reason, body)
1605
1606    def set_cors_xml(self, cors_xml, headers=None):
1607        """
1608        Set the CORS (Cross-Origin Resource Sharing) for a bucket.
1609
1610        :type cors_xml: str
1611        :param cors_xml: The XML document describing your desired
1612            CORS configuration.  See the S3 documentation for details
1613            of the exact syntax required.
1614        """
1615        fp = StringIO(cors_xml)
1616        md5 = boto.utils.compute_md5(fp)
1617        if headers is None:
1618            headers = {}
1619        headers['Content-MD5'] = md5[1]
1620        headers['Content-Type'] = 'text/xml'
1621        response = self.connection.make_request('PUT', self.name,
1622                                                data=fp.getvalue(),
1623                                                query_args='cors',
1624                                                headers=headers)
1625        body = response.read()
1626        if response.status == 200:
1627            return True
1628        else:
1629            raise self.connection.provider.storage_response_error(
1630                response.status, response.reason, body)
1631
1632    def set_cors(self, cors_config, headers=None):
1633        """
1634        Set the CORS for this bucket given a boto CORSConfiguration
1635        object.
1636
1637        :type cors_config: :class:`boto.s3.cors.CORSConfiguration`
1638        :param cors_config: The CORS configuration you want
1639            to configure for this bucket.
1640        """
1641        return self.set_cors_xml(cors_config.to_xml())
1642
1643    def get_cors_xml(self, headers=None):
1644        """
1645        Returns the current CORS configuration on the bucket as an
1646        XML document.
1647        """
1648        response = self.connection.make_request('GET', self.name,
1649                query_args='cors', headers=headers)
1650        body = response.read()
1651        boto.log.debug(body)
1652        if response.status == 200:
1653            return body
1654        else:
1655            raise self.connection.provider.storage_response_error(
1656                response.status, response.reason, body)
1657
1658    def get_cors(self, headers=None):
1659        """
1660        Returns the current CORS configuration on the bucket.
1661
1662        :rtype: :class:`boto.s3.cors.CORSConfiguration`
1663        :returns: A CORSConfiguration object that describes all current
1664            CORS rules in effect for the bucket.
1665        """
1666        body = self.get_cors_xml(headers)
1667        cors = CORSConfiguration()
1668        h = handler.XmlHandler(cors, self)
1669        xml.sax.parseString(body, h)
1670        return cors
1671
1672    def delete_cors(self, headers=None):
1673        """
1674        Removes all CORS configuration from the bucket.
1675        """
1676        response = self.connection.make_request('DELETE', self.name,
1677                                                query_args='cors',
1678                                                headers=headers)
1679        body = response.read()
1680        boto.log.debug(body)
1681        if response.status == 204:
1682            return True
1683        else:
1684            raise self.connection.provider.storage_response_error(
1685                response.status, response.reason, body)
1686
1687    def initiate_multipart_upload(self, key_name, headers=None,
1688                                  reduced_redundancy=False,
1689                                  metadata=None, encrypt_key=False,
1690                                  policy=None):
1691        """
1692        Start a multipart upload operation.
1693
1694        .. note::
1695
1696            Note: After you initiate multipart upload and upload one or more
1697            parts, you must either complete or abort multipart upload in order
1698            to stop getting charged for storage of the uploaded parts. Only
1699            after you either complete or abort multipart upload, Amazon S3
1700            frees up the parts storage and stops charging you for the parts
1701            storage.
1702
1703        :type key_name: string
1704        :param key_name: The name of the key that will ultimately
1705            result from this multipart upload operation.  This will be
1706            exactly as the key appears in the bucket after the upload
1707            process has been completed.
1708
1709        :type headers: dict
1710        :param headers: Additional HTTP headers to send and store with the
1711            resulting key in S3.
1712
1713        :type reduced_redundancy: boolean
1714        :param reduced_redundancy: In multipart uploads, the storage
1715            class is specified when initiating the upload, not when
1716            uploading individual parts.  So if you want the resulting
1717            key to use the reduced redundancy storage class set this
1718            flag when you initiate the upload.
1719
1720        :type metadata: dict
1721        :param metadata: Any metadata that you would like to set on the key
1722            that results from the multipart upload.
1723
1724        :type encrypt_key: bool
1725        :param encrypt_key: If True, the new copy of the object will
1726            be encrypted on the server-side by S3 and will be stored
1727            in an encrypted form while at rest in S3.
1728
1729        :type policy: :class:`boto.s3.acl.CannedACLStrings`
1730        :param policy: A canned ACL policy that will be applied to the
1731            new key (once completed) in S3.
1732        """
1733        query_args = 'uploads'
1734        provider = self.connection.provider
1735        headers = headers or {}
1736        if policy:
1737            headers[provider.acl_header] = policy
1738        if reduced_redundancy:
1739            storage_class_header = provider.storage_class_header
1740            if storage_class_header:
1741                headers[storage_class_header] = 'REDUCED_REDUNDANCY'
1742            # TODO: what if the provider doesn't support reduced redundancy?
1743            # (see boto.s3.key.Key.set_contents_from_file)
1744        if encrypt_key:
1745            headers[provider.server_side_encryption_header] = 'AES256'
1746        if metadata is None:
1747            metadata = {}
1748
1749        headers = boto.utils.merge_meta(headers, metadata,
1750                self.connection.provider)
1751        response = self.connection.make_request('POST', self.name, key_name,
1752                                                query_args=query_args,
1753                                                headers=headers)
1754        body = response.read()
1755        boto.log.debug(body)
1756        if response.status == 200:
1757            resp = MultiPartUpload(self)
1758            h = handler.XmlHandler(resp, self)
1759            if not isinstance(body, bytes):
1760                body = body.encode('utf-8')
1761            xml.sax.parseString(body, h)
1762            return resp
1763        else:
1764            raise self.connection.provider.storage_response_error(
1765                response.status, response.reason, body)
1766
1767    def complete_multipart_upload(self, key_name, upload_id,
1768                                  xml_body, headers=None):
1769        """
1770        Complete a multipart upload operation.
1771        """
1772        query_args = 'uploadId=%s' % upload_id
1773        if headers is None:
1774            headers = {}
1775        headers['Content-Type'] = 'text/xml'
1776        response = self.connection.make_request('POST', self.name, key_name,
1777                                                query_args=query_args,
1778                                                headers=headers, data=xml_body)
1779        contains_error = False
1780        body = response.read().decode('utf-8')
1781        # Some errors will be reported in the body of the response
1782        # even though the HTTP response code is 200.  This check
1783        # does a quick and dirty peek in the body for an error element.
1784        if body.find('<Error>') > 0:
1785            contains_error = True
1786        boto.log.debug(body)
1787        if response.status == 200 and not contains_error:
1788            resp = CompleteMultiPartUpload(self)
1789            h = handler.XmlHandler(resp, self)
1790            if not isinstance(body, bytes):
1791                body = body.encode('utf-8')
1792            xml.sax.parseString(body, h)
1793            # Use a dummy key to parse various response headers
1794            # for versioning, encryption info and then explicitly
1795            # set the completed MPU object values from key.
1796            k = self.key_class(self)
1797            k.handle_version_headers(response)
1798            k.handle_encryption_headers(response)
1799            resp.version_id = k.version_id
1800            resp.encrypted = k.encrypted
1801            return resp
1802        else:
1803            raise self.connection.provider.storage_response_error(
1804                response.status, response.reason, body)
1805
1806    def cancel_multipart_upload(self, key_name, upload_id, headers=None):
1807        """
1808        To verify that all parts have been removed, so you don't get charged
1809        for the part storage, you should call the List Parts operation and
1810        ensure the parts list is empty.
1811        """
1812        query_args = 'uploadId=%s' % upload_id
1813        response = self.connection.make_request('DELETE', self.name, key_name,
1814                                                query_args=query_args,
1815                                                headers=headers)
1816        body = response.read()
1817        boto.log.debug(body)
1818        if response.status != 204:
1819            raise self.connection.provider.storage_response_error(
1820                response.status, response.reason, body)
1821
1822    def delete(self, headers=None):
1823        return self.connection.delete_bucket(self.name, headers=headers)
1824
1825    def get_tags(self):
1826        response = self.get_xml_tags()
1827        tags = Tags()
1828        h = handler.XmlHandler(tags, self)
1829        if not isinstance(response, bytes):
1830            response = response.encode('utf-8')
1831        xml.sax.parseString(response, h)
1832        return tags
1833
1834    def get_xml_tags(self):
1835        response = self.connection.make_request('GET', self.name,
1836                                                query_args='tagging',
1837                                                headers=None)
1838        body = response.read()
1839        if response.status == 200:
1840            return body
1841        else:
1842            raise self.connection.provider.storage_response_error(
1843                response.status, response.reason, body)
1844
1845    def set_xml_tags(self, tag_str, headers=None, query_args='tagging'):
1846        if headers is None:
1847            headers = {}
1848        md5 = boto.utils.compute_md5(StringIO(tag_str))
1849        headers['Content-MD5'] = md5[1]
1850        headers['Content-Type'] = 'text/xml'
1851        if not isinstance(tag_str, bytes):
1852            tag_str = tag_str.encode('utf-8')
1853        response = self.connection.make_request('PUT', self.name,
1854                                                data=tag_str,
1855                                                query_args=query_args,
1856                                                headers=headers)
1857        body = response.read()
1858        if response.status != 204:
1859            raise self.connection.provider.storage_response_error(
1860                response.status, response.reason, body)
1861        return True
1862
1863    def set_tags(self, tags, headers=None):
1864        return self.set_xml_tags(tags.to_xml(), headers=headers)
1865
1866    def delete_tags(self, headers=None):
1867        response = self.connection.make_request('DELETE', self.name,
1868                                                query_args='tagging',
1869                                                headers=headers)
1870        body = response.read()
1871        boto.log.debug(body)
1872        if response.status == 204:
1873            return True
1874        else:
1875            raise self.connection.provider.storage_response_error(
1876                response.status, response.reason, body)
1877