def _gen_comp_xml(self, etags): elem = Element('CompleteMultipartUpload') for i, etag in enumerate(etags): elem_part = SubElement(elem, 'Part') SubElement(elem_part, 'PartNumber').text = str(i + 1) SubElement(elem_part, 'ETag').text = etag return tostring(elem)
def _versioning_PUT_error(self, path): # Root tag is not VersioningConfiguration elem = Element('foo') SubElement(elem, 'Status').text = 'Enabled' xml = tostring(elem) req = Request.blank('%s?versioning' % path, environ={'REQUEST_METHOD': 'PUT'}, headers={ 'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header() }, body=xml) status, headers, body = self.call_swift3(req) self.assertEqual(status.split()[0], '400') # Status is not "Enabled" or "Suspended" elem = Element('VersioningConfiguration') SubElement(elem, 'Status').text = 'enabled' xml = tostring(elem) req = Request.blank('%s?versioning' % path, environ={'REQUEST_METHOD': 'PUT'}, headers={ 'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header() }, body=xml) status, headers, body = self.call_swift3(req) self.assertEqual(status.split()[0], '400')
def _gen_invalid_multi_delete_xml(self, hasObjectTag=False): elem = Element('Delete') if hasObjectTag: obj = SubElement(elem, 'Object') SubElement(obj, 'Key').text = '' return tostring(elem, use_s3ns=False)
def POST(self, req): """ Handles Initiate Multipart Upload. """ # Create a unique S3 upload id from UUID to avoid duplicates. upload_id = unique_id() container = req.container_name + '+segments' try: req.get_response(self.app, 'PUT', container, '') except BucketAlreadyExists: pass obj = '%s/%s' % (req.object_name, upload_id) req.get_response(self.app, 'PUT', container, obj, body='') result_elem = Element('InitiateMultipartUploadResult') SubElement(result_elem, 'Bucket').text = req.container_name SubElement(result_elem, 'Key').text = req.object_name SubElement(result_elem, 'UploadId').text = upload_id body = tostring(result_elem) return HTTPOk(body=body, content_type='application/xml')
def _test_object_multi_DELETE(self, account): self.keys = ['Key1', 'Key2'] self.swift.register('DELETE', '/v1/AUTH_test/bucket/%s' % self.keys[0], swob.HTTPNoContent, {}, None) self.swift.register('DELETE', '/v1/AUTH_test/bucket/%s' % self.keys[1], swob.HTTPNotFound, {}, None) elem = Element('Delete') for key in self.keys: obj = SubElement(elem, 'Object') SubElement(obj, 'Key').text = key body = tostring(elem, use_s3ns=False) content_md5 = md5(body).digest().encode('base64').strip() req = Request.blank('/bucket?delete', environ={'REQUEST_METHOD': 'POST'}, headers={ 'Authorization': 'AWS %s:hmac' % account, 'Date': self.get_date_header(), 'Content-MD5': content_md5 }, body=body) req.date = datetime.now() req.content_type = 'text/plain' return self.call_swift3(req)
def POST(self, req): """ Handles Initiate Multipart Upload. """ log_s3api_command(req, 'create-multipart-upload') # Create a unique S3 upload id from UUID to avoid duplicates. upload_id = unique_id() container = req.container_name + MULTIUPLOAD_SUFFIX obj = '%s/%s' % (req.object_name, upload_id) if HTTP_HEADER_TAGGING_KEY in req.headers: tagging = convert_urlquery_to_xml( req.headers.get(HTTP_HEADER_TAGGING_KEY)) req.headers[OBJECT_TAGGING_HEADER] = tagging req.headers.pop('Etag', None) req.headers.pop('Content-Md5', None) req.environ['oio.ephemeral_object'] = True req.get_response(self.app, 'PUT', container, obj, body='') result_elem = Element('InitiateMultipartUploadResult') SubElement(result_elem, 'Bucket').text = req.container_name SubElement(result_elem, 'Key').text = req.object_name SubElement(result_elem, 'UploadId').text = upload_id body = tostring(result_elem) return HTTPOk(body=body, content_type='application/xml')
def append_copy_resp_body(self, controller_name, last_modified): elem = Element('Copy%sResult' % controller_name) SubElement(elem, 'LastModified').text = last_modified SubElement(elem, 'ETag').text = '"%s"' % self.etag self.headers['Content-Type'] = 'application/xml' self.body = tostring(elem) self.etag = None
def test_object_multi_DELETE_quiet(self): self.swift.register('DELETE', '/v1/AUTH_test/bucket/Key1', swob.HTTPNoContent, {}, None) self.swift.register('DELETE', '/v1/AUTH_test/bucket/Key2', swob.HTTPNotFound, {}, None) elem = Element('Delete') SubElement(elem, 'Quiet').text = 'true' for key in ['Key1', 'Key2']: obj = SubElement(elem, 'Object') SubElement(obj, 'Key').text = key body = tostring(elem, use_s3ns=False) content_md5 = md5(body).digest().encode('base64').strip() req = Request.blank('/bucket?delete', environ={'REQUEST_METHOD': 'POST'}, headers={ 'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header(), 'Content-MD5': content_md5 }, body=body) status, headers, body = self.call_swift3(req) self.assertEquals(status.split()[0], '200') elem = fromstring(body) self.assertEquals(len(elem.findall('Deleted')), 0)
def test_bucket_acl_PUT(self): elem = Element('AccessControlPolicy') owner = SubElement(elem, 'Owner') SubElement(owner, 'ID').text = 'id' acl = SubElement(elem, 'AccessControlList') grant = SubElement(acl, 'Grant') grantee = SubElement(grant, 'Grantee', nsmap={'xsi': XMLNS_XSI}) grantee.set('{%s}type' % XMLNS_XSI, 'Group') SubElement(grantee, 'URI').text = \ 'http://acs.amazonaws.com/groups/global/AllUsers' SubElement(grant, 'Permission').text = 'READ' xml = tostring(elem) req = Request.blank('/bucket?acl', environ={'REQUEST_METHOD': 'PUT'}, headers={'Authorization': 'AWS test:tester:hmac'}, body=xml) status, headers, body = self.call_swift3(req) self.assertEquals(status.split()[0], '200') req = Request.blank('/bucket?acl', environ={'REQUEST_METHOD': 'PUT', 'wsgi.input': StringIO(xml)}, headers={'Authorization': 'AWS test:tester:hmac', 'Transfer-Encoding': 'chunked'}) self.assertIsNone(req.content_length) self.assertIsNone(req.message_length()) status, headers, body = self.call_swift3(req) self.assertEquals(status.split()[0], '200')
def test_bucket_acl_PUT(self): elem = Element('AccessControlPolicy') owner = SubElement(elem, 'Owner') SubElement(owner, 'ID').text = 'id' acl = SubElement(elem, 'AccessControlList') grant = SubElement(acl, 'Grant') grantee = SubElement(grant, 'Grantee', nsmap={'xsi': XMLNS_XSI}) grantee.set('{%s}type' % XMLNS_XSI, 'Group') SubElement(grantee, 'URI').text = \ 'http://acs.amazonaws.com/groups/global/AllUsers' SubElement(grant, 'Permission').text = 'READ' xml = tostring(elem) req = Request.blank('/bucket?acl', environ={'REQUEST_METHOD': 'PUT'}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header()}, body=xml) status, headers, body = self.call_swift3(req) self.assertEqual(status.split()[0], '200') req = Request.blank('/bucket?acl', environ={'REQUEST_METHOD': 'PUT', 'wsgi.input': StringIO(xml)}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header(), 'Transfer-Encoding': 'chunked'}) self.assertIsNone(req.content_length) self.assertIsNone(req.message_length()) status, headers, body = self.call_swift3(req) self.assertEqual(status.split()[0], '200')
def _build_tagging_body(self, n_tags=1): elem = Element('Tagging') sub = SubElement(elem, 'TagSet') for num in range(n_tags): tag = SubElement(sub, 'Tag') SubElement(tag, 'Key').text = 'key' * 41 + '%05d' % num SubElement(tag, 'Value').text = 'value' * 50 + '%06d' % num return tostring(elem)
def _gen_multi_delete_xml(self, objects, quiet=None): elem = Element('Delete') if quiet: SubElement(elem, 'Quiet').text = quiet for key in objects: obj = SubElement(elem, 'Object') SubElement(obj, 'Key').text = key return tostring(elem, use_s3ns=False)
def _body_iter(self): error_elem = Element('Error') SubElement(error_elem, 'Code').text = self._code SubElement(error_elem, 'Message').text = self._msg if 'swift.trans_id' in self.environ: request_id = self.environ['swift.trans_id'] SubElement(error_elem, 'RequestId').text = request_id self._dict_to_etree(error_elem, self.info) yield tostring(error_elem, use_s3ns=False)
def _gen_error_body(self, error, elem, delete_list): for key, version in delete_list: if version is not None: # TODO: delete the specific version of the object raise S3NotImplemented() error_elem = SubElement(elem, 'Error') SubElement(error_elem, 'Key').text = key SubElement(error_elem, 'Code').text = error.__class__.__name__ SubElement(error_elem, 'Message').text = error._msg return tostring(elem)
def test_object_multi_DELETE_without_md5(self): elem = Element('Delete') for key in ['Key1', 'Key2']: obj = SubElement(elem, 'Object') SubElement(obj, 'Key').text = key body = tostring(elem, use_s3ns=False) req = Request.blank('/bucket?delete', environ={'REQUEST_METHOD': 'POST'}, headers={'Authorization': 'AWS test:tester:hmac'}, body=body) status, headers, body = self.call_swift3(req) self.assertEquals(self._get_error_code(body), 'InvalidRequest')
def _dict_to_etree(self, parent, d): for key, value in d.items(): tag = re.sub('\W', '', snake_to_camel(key)) elem = SubElement(parent, tag) if isinstance(value, (dict, DictMixin)): self._dict_to_etree(elem, value) else: try: elem.text = str(value) except ValueError: # We set an invalid string for XML. elem.text = '(invalid string)'
def elem(self): """ Decode the value to an ACL instance. """ elem = Element(self.root_tag) owner = SubElement(elem, 'Owner') SubElement(owner, 'ID').text = self.owner.id SubElement(owner, 'DisplayName').text = self.owner.name SubElement(elem, 'AccessControlList').extend(g.elem() for g in self.grants) return elem
def _handle_create_multipart_upload(self, s3_key, req): req.headers.setdefault('content-type', 'application/octet-stream') boto3_resp = self.local_to_me_provider._create_multipart_upload( req.headers, s3_key) # NOTE: we forced swift3 to fully delegate to us, so we are # responsible for returning a valid S3 API response. result_elem = Element('InitiateMultipartUploadResult') SubElement(result_elem, 'Bucket').text = self.container_name SubElement(result_elem, 'Key').text = self.object_name SubElement(result_elem, 'UploadId').text = boto3_resp['UploadId'] body = tostring(result_elem) # Note: swift3 mw requires obj POST to return 202 return swob.HTTPAccepted(body=body, content_type='application/xml')
def convert_urlquery_to_xml(val): """Convert x-amz-tagging to a Tagging XML.""" root = Element('Tagging') elem = SubElement(root, 'TagSet') # AWS support key1=&key2= items = parse_qs(val, keep_blank_values=True) for key, val in items.items(): if len(val) != 1: raise InvalidArgument(HTTP_HEADER_TAGGING_KEY, value=val, msg=INVALID_TAGGING) tag = SubElement(elem, 'Tag') SubElement(tag, 'Key').text = key SubElement(tag, 'Value').text = val[0] return tostring(root)
def _make_xml(grantee): owner = 'test:tester' permission = 'READ' elem = Element('AccessControlPolicy') elem_owner = SubElement(elem, 'Owner') SubElement(elem_owner, 'ID').text = owner SubElement(elem_owner, 'DisplayName').text = owner acl_list_elem = SubElement(elem, 'AccessControlList') elem_grant = SubElement(acl_list_elem, 'Grant') elem_grant.append(grantee) SubElement(elem_grant, 'Permission').text = permission return tostring(elem)
def test_object_multi_DELETE_too_many_keys(self): elem = Element('Delete') for i in range(CONF.max_multi_delete_objects + 1): obj = SubElement(elem, 'Object') SubElement(obj, 'Key').text = str(i) body = tostring(elem, use_s3ns=False) content_md5 = md5(body).digest().encode('base64').strip() req = Request.blank('/bucket?delete', environ={'REQUEST_METHOD': 'POST'}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header(), 'Content-MD5': content_md5}, body=body) status, headers, body = self.call_swift3(req) self.assertEqual(self._get_error_code(body), 'MalformedXML')
def test_object_multi_DELETE_to_object(self): elem = Element('Delete') obj = SubElement(elem, 'Object') SubElement(obj, 'Key').text = 'object' body = tostring(elem, use_s3ns=False) content_md5 = md5(body).digest().encode('base64').strip() req = Request.blank('/bucket/object?delete', environ={'REQUEST_METHOD': 'POST'}, headers={'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header(), 'Content-MD5': content_md5}, body=body) status, headers, body = self.call_swift3(req) self.assertEqual(status.split()[0], '200')
def GET(self, req): # pylint: disable=invalid-name """ Handles GET Bucket tagging and GET Object tagging. """ resp = req.get_versioned_response(self.app, 'HEAD', req.container_name, req.object_name) headers = dict() if req.is_object_request: body = resp.sysmeta_headers.get(OBJECT_TAGGING_HEADER) # It seems that S3 returns x-amz-version-id, # even if it is not documented. headers['x-amz-version-id'] = resp.sw_headers[VERSION_ID_HEADER] else: body = resp.sysmeta_headers.get(BUCKET_TAGGING_HEADER) close_if_possible(resp.app_iter) if not body: if not req.is_object_request: raise NoSuchTagSet(headers=headers) else: elem = Element('Tagging') SubElement(elem, 'TagSet') body = tostring(elem) return HTTPOk(body=body, content_type='application/xml', headers=headers)
def _versioning_PUT_suspended(self, path): elem = Element('VersioningConfiguration') SubElement(elem, 'Status').text = 'Suspended' xml = tostring(elem) self.swift.register('HEAD', '/v1/AUTH_test/%s' % VERSIONING_BUCKET, HTTPNotFound, {}, None) self.swift.register('PUT', '/v1/AUTH_test/%s' % VERSIONING_BUCKET, HTTPCreated, {}, None) self.swift.register('POST', '/v1/AUTH_test/bucket', HTTPNoContent, {}, None) req = Request.blank('%s?versioning' % path, environ={'REQUEST_METHOD': 'PUT'}, headers={ 'Authorization': 'AWS test:tester:hmac', 'Date': self.get_date_header() }, body=xml) status, headers, body = self.call_swift3(req) self.assertEqual(status.split()[0], '200') calls = self.swift.calls_with_headers self.assertEqual(calls[-1][0], 'POST') self.assertIn(('X-Remove-History-Location', 'true'), calls[-1][2].items())
def GET(self, req): """ Handles GET Bucket versioning. """ log_s3api_command(req, 'get-bucket-versioning') info = req.get_container_info(self.app) status = None versions_container = info.get('sysmeta', {}).get('versions-location') if versions_container: status = 'Enabled' else: versions_container = ''.join( [req.container_name, VERSIONING_SUFFIX]) try: req.get_response(self.app, 'HEAD', container=versions_container) status = 'Suspended' except NoSuchBucket: pass # Just report there is no versioning configured here. elem = Element('VersioningConfiguration') if status: SubElement(elem, 'Status').text = status body = tostring(elem) return HTTPOk(body=body, content_type="text/plain")
def _handle_complete_multipart_upload(self, s3_key, req): upload_id = req.params['uploadId'] parts = [] xml = req.environ[CC_SWIFT_REQ_KEY].xml(MAX_COMPLETE_UPLOAD_BODY_SIZE) complete_elem = fromstring(xml, 'CompleteMultipartUpload') for part_elem in complete_elem.iterchildren('Part'): part_number = int(part_elem.find('./PartNumber').text) etag = part_elem.find('./ETag').text parts.append({'ETag': etag, 'PartNumber': part_number}) boto3_resp = self.local_to_me_provider._complete_multipart_upload( s3_key, upload_id, parts) # NOTE: we forced swift3 to fully delegate to us, so we are # responsible for returning a valid S3 API response. # NOTE (for the note): this workaround for a client library (boto) was # copied verbatim from the "swift3" codebase. It may or may not be # relevant (I think it is for any client talking to cloud-connector # using boto and a non-default port number), but this comment and the # workaround both come from "swift3" and should be safe for us as well. # (sic) vvvvvvvvvvvvvvvvvvvvvvvvvv # NOTE: boto with sig v4 appends port to HTTP_HOST value at the # request header when the port is non default value and it # makes req.host_url like as http://localhost:8080:8080/path # that obviously invalid. Probably it should be resolved at # swift.common.swob though, tentatively we are parsing and # reconstructing the correct host_url info here. # in detail, https://github.com/boto/boto/pull/3513 # (sic) ^^^^^^^^^^^^^^^^^^^^^^^^^^ parsed_url = urlparse(req.host_url) host_url = '%s://%s' % (parsed_url.scheme, parsed_url.hostname) if parsed_url.port: host_url += ':%s' % parsed_url.port result_elem = Element('CompleteMultipartUploadResult') SubElement(result_elem, 'Location').text = host_url + req.path SubElement(result_elem, 'Bucket').text = self.container_name SubElement(result_elem, 'Key').text = self.object_name SubElement(result_elem, 'ETag').text = boto3_resp['ETag'] body = tostring(result_elem) # Note: swift3 mw requires obj POST to return 202 return swob.HTTPAccepted(body=body, content_type='application/xml')
def GET(self, req): """ Handle GET Service request """ resp = req.get_response(self.app, query={'format': 'json'}) containers = loads(resp.body) # we don't keep the creation time of a backet (s3cmd doesn't # work without that) so we use something bogus. elem = Element('ListAllMyBucketsResult') owner = SubElement(elem, 'Owner') SubElement(owner, 'ID').text = req.user_id SubElement(owner, 'DisplayName').text = req.user_id buckets = SubElement(elem, 'Buckets') for c in containers: bucket = SubElement(buckets, 'Bucket') SubElement(bucket, 'Name').text = c['name'] SubElement(bucket, 'CreationDate').text = \ '2009-02-03T16:45:09.000Z' body = tostring(elem) return HTTPOk(content_type='application/xml', body=body)
def elem(self): """ Create an etree element. """ elem = Element('Grant') elem.append(self.grantee.elem()) SubElement(elem, 'Permission').text = self.permission return elem
def test_bucket_acl_PUT(self): elem = Element('AccessControlPolicy') owner = SubElement(elem, 'Owner') SubElement(owner, 'ID').text = 'id' acl = SubElement(elem, 'AccessControlList') grant = SubElement(acl, 'Grant') grantee = SubElement(grant, 'Grantee', nsmap={'xsi': XMLNS_XSI}) grantee.set('{%s}type' % XMLNS_XSI, 'Group') SubElement(grantee, 'URI').text = \ 'http://acs.amazonaws.com/groups/global/AllUsers' SubElement(grant, 'Permission').text = 'READ' xml = tostring(elem) req = Request.blank('/bucket?acl', environ={'REQUEST_METHOD': 'PUT'}, headers={'Authorization': 'AWS test:tester:hmac'}, body=xml) status, headers, body = self.call_swift3(req) self.assertEquals(status.split()[0], '200')
def test_versioning_put(self): # Versioning not configured status, headers, body = self.conn.make_request('GET', 'bucket', query='versioning') self.assertEqual(status, 200) elem = fromstring(body) self.assertEqual(elem.getchildren(), []) # Enable versioning elem = Element('VersioningConfiguration') SubElement(elem, 'Status').text = 'Enabled' xml = tostring(elem) status, headers, body = self.conn.make_request('PUT', 'bucket', body=xml, query='versioning') self.assertEqual(status, 200) status, headers, body = self.conn.make_request('GET', 'bucket', query='versioning') self.assertEqual(status, 200) elem = fromstring(body) self.assertEqual(elem.find('./Status').text, 'Enabled') # Suspend versioning elem = Element('VersioningConfiguration') SubElement(elem, 'Status').text = 'Suspended' xml = tostring(elem) status, headers, body = self.conn.make_request('PUT', 'bucket', body=xml, query='versioning') self.assertEqual(status, 200) status, headers, body = self.conn.make_request('GET', 'bucket', query='versioning') self.assertEqual(status, 200) elem = fromstring(body) self.assertEqual(elem.find('./Status').text, 'Suspended')
def test_bucket_PUT_with_location(self): elem = Element('CreateBucketConfiguration') SubElement(elem, 'LocationConstraint').text = 'US' xml = tostring(elem) req = Request.blank('/bucket', environ={'REQUEST_METHOD': 'PUT'}, headers={'Authorization': 'AWS test:tester:hmac'}, body=xml) status, headers, body = self.call_swift3(req) self.assertEquals(status.split()[0], '200')
def test_bucket_fails_with_both_acl_header_and_xml_PUT(self): elem = Element('AccessControlPolicy') owner = SubElement(elem, 'Owner') SubElement(owner, 'ID').text = 'id' acl = SubElement(elem, 'AccessControlList') grant = SubElement(acl, 'Grant') grantee = SubElement(grant, 'Grantee', nsmap={'xsi': XMLNS_XSI}) grantee.set('{%s}type' % XMLNS_XSI, 'Group') SubElement(grantee, 'URI').text = \ 'http://acs.amazonaws.com/groups/global/AllUsers' SubElement(grant, 'Permission').text = 'READ' xml = tostring(elem) req = Request.blank('/bucket?acl', environ={'REQUEST_METHOD': 'PUT'}, headers={'Authorization': 'AWS test:tester:hmac', 'X-AMZ-ACL': 'public-read'}, body=xml) status, headers, body = self.call_swift3(req) self.assertEquals(self._get_error_code(body), 'UnexpectedContent')
def get_acl(account_name, headers): """ Attempts to construct an S3 ACL based on what is found in the swift headers """ elem = Element('AccessControlPolicy') owner = SubElement(elem, 'Owner') SubElement(owner, 'ID').text = account_name SubElement(owner, 'DisplayName').text = account_name access_control_list = SubElement(elem, 'AccessControlList') # grant FULL_CONTROL to myself by default grant = SubElement(access_control_list, 'Grant') grantee = SubElement(grant, 'Grantee', nsmap={'xsi': XMLNS_XSI}) grantee.set('{%s}type' % XMLNS_XSI, 'CanonicalUser') SubElement(grantee, 'ID').text = account_name SubElement(grantee, 'DisplayName').text = account_name SubElement(grant, 'Permission').text = 'FULL_CONTROL' referrers, _ = parse_acl(headers.get('x-container-read')) if referrer_allowed('unknown', referrers): # grant public-read access grant = SubElement(access_control_list, 'Grant') grantee = SubElement(grant, 'Grantee', nsmap={'xsi': XMLNS_XSI}) grantee.set('{%s}type' % XMLNS_XSI, 'Group') SubElement(grantee, 'URI').text = \ 'http://acs.amazonaws.com/groups/global/AllUsers' SubElement(grant, 'Permission').text = 'READ' referrers, _ = parse_acl(headers.get('x-container-write')) if referrer_allowed('unknown', referrers): # grant public-write access grant = SubElement(access_control_list, 'Grant') grantee = SubElement(grant, 'Grantee', nsmap={'xsi': XMLNS_XSI}) grantee.set('{%s}type' % XMLNS_XSI, 'Group') SubElement(grantee, 'URI').text = \ 'http://acs.amazonaws.com/groups/global/AllUsers' SubElement(grant, 'Permission').text = 'WRITE' body = tostring(elem) return HTTPOk(body=body, content_type="text/plain")
def add_xml_element(self, parent): grantee_elem = SubElement(parent, 'Grantee', nsmap={'xsi': XMLNS_XSI}) grantee_elem.set('xsi:type', self.type) return grantee_elem
def GET(self, req): if req.object_name: raise InvalidRequest('There is no such thing as the ?versions ' 'sub-resource for a key') max_keys = utils.DEFAULT_MAX_BUCKET_LISTING if 'max-keys' in req.params: try: max_keys = int(req.params['max-keys']) if max_keys < 0 or utils.MAX_MAX_BUCKET_LISTING < max_keys: raise Exception() except Exception: err_msg = 'Provided max-keys not an integer or within ' \ 'integer range' raise InvalidArgument('max-keys', req.params['max-keys'], err_msg) encoding_type = req.params.get('encoding-type') if encoding_type is not None and encoding_type != 'url': err_msg = 'Invalid Encoding Method specified in Request' raise InvalidArgument('encoding-type', encoding_type, err_msg) prefix = req.params.get('prefix', '') key_marker = req.params.get('key-marker', '') version_id_marker = req.params.get('version-id-marker') delimiter = req.params.get('delimiter') versions = req.collect_versions(self.app) versions = [v for v in versions if v[0].startswith(prefix)] if version_id_marker is not None: versions = [v for v in versions if v[0] > key_marker or v[0] == key_marker and v[1] > version_id_marker] else: versions = [v for v in versions if v[0] > key_marker] version_result = [] for v in versions: key = v[0] if delimiter and delimiter in key[len(prefix):]: common_prefix = prefix common_prefix += key[len(prefix):].split(delimiter)[0] common_prefix += delimiter if common_prefix not in version_result: version_result.append(common_prefix) else: version_result.append(v) if len(version_result) > max_keys: version_result = version_result[:max_keys] is_truncated = True else: is_truncated = False result_elem = Element('ListVersionsResult', encoding_type=encoding_type) SubElement(result_elem, 'Name').text = req.container_name SubElement(result_elem, 'Prefix').text = prefix SubElement(result_elem, 'KeyMarker').text = key_marker version_id_marker_elem = SubElement(result_elem, 'VersionIdMarker') if version_id_marker is not None: version_id_marker_elem.text = version_id_marker if is_truncated: v = version_result[-1] SubElement(result_elem, 'NextKeyMarker').text = v[0] if len(v) > 1: SubElement(result_elem, 'NextVersionIdMarker').text = v[1] SubElement(result_elem, 'MaxKeys').text = str(max_keys) if delimiter is not None: SubElement(result_elem, 'Delimiter').text = delimiter if encoding_type is not None: SubElement(result_elem, 'EncodingType').text = encoding_type SubElement(result_elem, 'IsTruncated').text = \ 'true' if is_truncated else 'false' for key, v_id, delete, is_latest, lmodified, etag, size, owner \ in (v for v in version_result if isinstance(v, tuple)): if delete: version_elem = SubElement(result_elem, 'DeleteMarker') else: version_elem = SubElement(result_elem, 'Version') SubElement(version_elem, 'Key').text = key SubElement(version_elem, 'VersionId').text = v_id SubElement(version_elem, 'IsLatest').text = is_latest SubElement(version_elem, 'LastModified').text = \ lmodified[:-3] + 'Z' if not delete: SubElement(version_elem, 'ETag').text = etag SubElement(version_elem, 'Size').text = str(size) owner_elem = SubElement(version_elem, 'Owner') SubElement(owner_elem, 'ID').text = owner SubElement(owner_elem, 'DisplayName').text = owner if not delete: SubElement(version_elem, 'StorageClass').text = 'STANDARD' for common_prefix in (v for v in version_result if isinstance(v, str)): elem = SubElement(result_elem, 'CommonPrefixes') SubElement(elem, 'Prefix').text = common_prefix xml = tostring(result_elem) return HTTPOk(body=xml)