Use lxml for processing XML
The lxml XML toolkit is a binding for the C libraries libxml2 and libxslt. It is compatible with ElementTree API, which is more pythonic than the DOM interface. In addition, lxml has attractive features like XML validation and namespace mapping. Replacing the current DOM interface with the lxml API will simplify Swift3 code a lot and improve maintainability, I believe. Change-Id: Ie2291f180421559ed3320a173d2f7eea81f459d9
This commit is contained in:
parent
2e78cd0355
commit
1f87522631
@ -1 +1,2 @@
|
|||||||
swift>=1.8
|
swift>=1.8
|
||||||
|
lxml
|
||||||
|
@ -54,8 +54,7 @@ following for an SAIO setup::
|
|||||||
|
|
||||||
from urllib import quote
|
from urllib import quote
|
||||||
import base64
|
import base64
|
||||||
from xml.sax.saxutils import escape as xml_escape
|
from lxml.etree import fromstring, tostring, Element, SubElement
|
||||||
from xml.dom.minidom import parseString
|
|
||||||
|
|
||||||
from simplejson import loads
|
from simplejson import loads
|
||||||
import email.utils
|
import email.utils
|
||||||
@ -71,7 +70,9 @@ from swift.common.http import HTTP_OK, HTTP_CREATED, HTTP_ACCEPTED, \
|
|||||||
HTTP_NO_CONTENT, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, HTTP_NOT_FOUND, \
|
HTTP_NO_CONTENT, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, HTTP_NOT_FOUND, \
|
||||||
HTTP_CONFLICT, HTTP_UNPROCESSABLE_ENTITY, is_success, \
|
HTTP_CONFLICT, HTTP_UNPROCESSABLE_ENTITY, is_success, \
|
||||||
HTTP_REQUEST_ENTITY_TOO_LARGE
|
HTTP_REQUEST_ENTITY_TOO_LARGE
|
||||||
|
from swift.common.middleware.acl import parse_acl, referrer_allowed
|
||||||
|
|
||||||
|
XMLNS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
|
||||||
|
|
||||||
MAX_BUCKET_LISTING = 1000
|
MAX_BUCKET_LISTING = 1000
|
||||||
|
|
||||||
@ -136,128 +137,64 @@ def get_err_response(code):
|
|||||||
(HTTPServiceUnavailable, 'Please reduce your request rate')}
|
(HTTPServiceUnavailable, 'Please reduce your request rate')}
|
||||||
|
|
||||||
resp, message = error_table[code]
|
resp, message = error_table[code]
|
||||||
body = '<?xml version="1.0" encoding="UTF-8"?>\r\n<Error>\r\n ' \
|
|
||||||
'<Code>%s</Code>\r\n <Message>%s</Message>\r\n</Error>\r\n' \
|
elem = Element('Error')
|
||||||
% (code, message)
|
SubElement(elem, 'Code').text = code
|
||||||
|
SubElement(elem, 'Message').text = message
|
||||||
|
body = tostring(elem, xml_declaration=True, encoding='UTF-8')
|
||||||
|
|
||||||
return resp(body=body, content_type='text/xml')
|
return resp(body=body, content_type='text/xml')
|
||||||
|
|
||||||
|
|
||||||
|
def add_canonical_user(parent, tag, user, nsmap=None):
|
||||||
|
"""
|
||||||
|
Create an element for cannonical user.
|
||||||
|
"""
|
||||||
|
elem = SubElement(parent, tag, nsmap=nsmap)
|
||||||
|
SubElement(elem, 'ID').text = user
|
||||||
|
SubElement(elem, 'DisplayName').text = user
|
||||||
|
|
||||||
|
return elem
|
||||||
|
|
||||||
|
|
||||||
def get_acl(account_name, headers):
|
def get_acl(account_name, headers):
|
||||||
"""
|
"""
|
||||||
Attempts to construct an S3 ACL based on what is found in the swift headers
|
Attempts to construct an S3 ACL based on what is found in the swift headers
|
||||||
"""
|
"""
|
||||||
|
|
||||||
acl = 'private' # default to private
|
elem = Element('AccessControlPolicy')
|
||||||
|
add_canonical_user(elem, 'Owner', account_name)
|
||||||
|
access_control_list = SubElement(elem, 'AccessControlList')
|
||||||
|
|
||||||
if 'x-container-read' in headers:
|
# grant FULL_CONTROL to myself by default
|
||||||
if headers['x-container-read'] == ".r:*" or\
|
grant = SubElement(access_control_list, 'Grant')
|
||||||
".r:*," in headers['x-container-read'] or \
|
grantee = add_canonical_user(grant, 'Grantee', account_name,
|
||||||
",*," in headers['x-container-read']:
|
nsmap={'xsi': XMLNS_XSI})
|
||||||
acl = 'public-read'
|
grantee.set('{%s}type' % XMLNS_XSI, 'CanonicalUser')
|
||||||
if 'x-container-write' in headers:
|
SubElement(grant, 'Permission').text = 'FULL_CONTROL'
|
||||||
if headers['x-container-write'] == ".r:*" or\
|
|
||||||
".r:*," in headers['x-container-write'] or \
|
referrers, _ = parse_acl(headers.get('x-container-read'))
|
||||||
",*," in headers['x-container-write']:
|
if referrer_allowed('unknown', referrers):
|
||||||
if acl == 'public-read':
|
# grant public-read access
|
||||||
acl = 'public-read-write'
|
grant = SubElement(access_control_list, 'Grant')
|
||||||
else:
|
grantee = SubElement(grant, 'Grantee', nsmap={'xsi': XMLNS_XSI})
|
||||||
acl = 'public-write'
|
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, xml_declaration=True, encoding='UTF-8')
|
||||||
|
|
||||||
if acl == 'private':
|
|
||||||
body = ('<AccessControlPolicy>'
|
|
||||||
'<Owner>'
|
|
||||||
'<ID>%s</ID>'
|
|
||||||
'<DisplayName>%s</DisplayName>'
|
|
||||||
'</Owner>'
|
|
||||||
'<AccessControlList>'
|
|
||||||
'<Grant>'
|
|
||||||
'<Grantee xmlns:xsi="http://www.w3.org/2001/'
|
|
||||||
'XMLSchema-instance" xsi:type="CanonicalUser">'
|
|
||||||
'<ID>%s</ID>'
|
|
||||||
'<DisplayName>%s</DisplayName>'
|
|
||||||
'</Grantee>'
|
|
||||||
'<Permission>FULL_CONTROL</Permission>'
|
|
||||||
'</Grant>'
|
|
||||||
'</AccessControlList>'
|
|
||||||
'</AccessControlPolicy>' %
|
|
||||||
(account_name, account_name, account_name, account_name))
|
|
||||||
elif acl == 'public-read':
|
|
||||||
body = ('<AccessControlPolicy>'
|
|
||||||
'<Owner>'
|
|
||||||
'<ID>%s</ID>'
|
|
||||||
'<DisplayName>%s</DisplayName>'
|
|
||||||
'</Owner>'
|
|
||||||
'<AccessControlList>'
|
|
||||||
'<Grant>'
|
|
||||||
'<Grantee xmlns:xsi="http://www.w3.org/2001/'
|
|
||||||
'XMLSchema-instance" xsi:type="CanonicalUser">'
|
|
||||||
'<ID>%s</ID>'
|
|
||||||
'<DisplayName>%s</DisplayName>'
|
|
||||||
'</Grantee>'
|
|
||||||
'<Permission>FULL_CONTROL</Permission>'
|
|
||||||
'</Grant>'
|
|
||||||
'<Grant>'
|
|
||||||
'<Grantee xmlns:xsi="http://www.w3.org/2001/'
|
|
||||||
'XMLSchema-instance" xsi:type="Group">'
|
|
||||||
'<URI>http://acs.amazonaws.com/groups/global/AllUsers</URI>'
|
|
||||||
'</Grantee>'
|
|
||||||
'<Permission>READ</Permission>'
|
|
||||||
'</Grant>'
|
|
||||||
'</AccessControlList>'
|
|
||||||
'</AccessControlPolicy>' %
|
|
||||||
(account_name, account_name, account_name, account_name))
|
|
||||||
elif acl == 'public-read-write':
|
|
||||||
body = ('<AccessControlPolicy>'
|
|
||||||
'<Owner>'
|
|
||||||
'<ID>%s</ID>'
|
|
||||||
'<DisplayName>%s</DisplayName>'
|
|
||||||
'</Owner>'
|
|
||||||
'<AccessControlList>'
|
|
||||||
'<Grant>'
|
|
||||||
'<Grantee xmlns:xsi="http://www.w3.org/2001/'
|
|
||||||
'XMLSchema-instance" xsi:type="CanonicalUser">'
|
|
||||||
'<ID>%s</ID>'
|
|
||||||
'<DisplayName>%s</DisplayName>'
|
|
||||||
'</Grantee>'
|
|
||||||
'<Permission>FULL_CONTROL</Permission>'
|
|
||||||
'</Grant>'
|
|
||||||
'<Grant>'
|
|
||||||
'<Grantee xmlns:xsi="http://www.w3.org/2001/'
|
|
||||||
'XMLSchema-instance" xsi:type="Group">'
|
|
||||||
'<URI>http://acs.amazonaws.com/groups/global/AllUsers</URI>'
|
|
||||||
'</Grantee>'
|
|
||||||
'<Permission>READ</Permission>'
|
|
||||||
'</Grant>'
|
|
||||||
'</AccessControlList>'
|
|
||||||
'<AccessControlList>'
|
|
||||||
'<Grant>'
|
|
||||||
'<Grantee xmlns:xsi="http://www.w3.org/2001/'
|
|
||||||
'XMLSchema-instance" xsi:type="Group">'
|
|
||||||
'<URI>http://acs.amazonaws.com/groups/global/AllUsers</URI>'
|
|
||||||
'</Grantee>'
|
|
||||||
'<Permission>WRITE</Permission>'
|
|
||||||
'</Grant>'
|
|
||||||
'</AccessControlList>'
|
|
||||||
'</AccessControlPolicy>' %
|
|
||||||
(account_name, account_name, account_name, account_name))
|
|
||||||
else:
|
|
||||||
body = ('<AccessControlPolicy>'
|
|
||||||
'<Owner>'
|
|
||||||
'<ID>%s</ID>'
|
|
||||||
'<DisplayName>%s</DisplayName>'
|
|
||||||
'</Owner>'
|
|
||||||
'<AccessControlList>'
|
|
||||||
'<Grant>'
|
|
||||||
'<Grantee xmlns:xsi="http://www.w3.org/2001/'
|
|
||||||
'XMLSchema-instance" xsi:type="CanonicalUser">'
|
|
||||||
'<ID>%s</ID>'
|
|
||||||
'<DisplayName>%s</DisplayName>'
|
|
||||||
'</Grantee>'
|
|
||||||
'<Permission>FULL_CONTROL</Permission>'
|
|
||||||
'</Grant>'
|
|
||||||
'</AccessControlList>'
|
|
||||||
'</AccessControlPolicy>' %
|
|
||||||
(account_name, account_name, account_name, account_name))
|
|
||||||
return HTTPOk(body=body, content_type="text/plain")
|
return HTTPOk(body=body, content_type="text/plain")
|
||||||
|
|
||||||
|
|
||||||
@ -322,13 +259,11 @@ def swift_acl_translate(acl, group='', user='', xml=False):
|
|||||||
['HTTP_X_CONTAINER_READ', '.']]
|
['HTTP_X_CONTAINER_READ', '.']]
|
||||||
if xml:
|
if xml:
|
||||||
# We are working with XML and need to parse it
|
# We are working with XML and need to parse it
|
||||||
dom = parseString(acl)
|
elem = fromstring(acl)
|
||||||
acl = 'unknown'
|
acl = 'unknown'
|
||||||
for grant in dom.getElementsByTagName('Grant'):
|
for grant in elem.findall('./AccessControlList/Grant'):
|
||||||
permission = grant.getElementsByTagName('Permission')[0]\
|
permission = grant.find('./Permission').text
|
||||||
.firstChild.data
|
grantee = grant.find('./Grantee').get('{%s}type' % XMLNS_XSI)
|
||||||
grantee = grant.getElementsByTagName('Grantee')[0]\
|
|
||||||
.getAttributeNode('xsi:type').nodeValue
|
|
||||||
if permission == "FULL_CONTROL" and grantee == 'CanonicalUser' and\
|
if permission == "FULL_CONTROL" and grantee == 'CanonicalUser' and\
|
||||||
acl != 'public-read' and acl != 'public-read-write':
|
acl != 'public-read' and acl != 'public-read-write':
|
||||||
acl = 'private'
|
acl = 'private'
|
||||||
@ -415,14 +350,16 @@ class ServiceController(Controller):
|
|||||||
containers = loads(resp.body)
|
containers = loads(resp.body)
|
||||||
# we don't keep the creation time of a backet (s3cmd doesn't
|
# we don't keep the creation time of a backet (s3cmd doesn't
|
||||||
# work without that) so we use something bogus.
|
# work without that) so we use something bogus.
|
||||||
body = '<?xml version="1.0" encoding="UTF-8"?>' \
|
elem = Element('ListAllMyBucketsResult')
|
||||||
'<ListAllMyBucketsResult ' \
|
buckets = SubElement(elem, 'Buckets')
|
||||||
'xmlns="http://doc.s3.amazonaws.com/2006-03-01">' \
|
for c in containers:
|
||||||
'<Buckets>%s</Buckets>' \
|
bucket = SubElement(buckets, 'Bucket')
|
||||||
'</ListAllMyBucketsResult>' \
|
SubElement(bucket, 'Name').text = c['name']
|
||||||
% ("".join(['<Bucket><Name>%s</Name><CreationDate>'
|
SubElement(bucket, 'CreationDate').text = \
|
||||||
'2009-02-03T16:45:09.000Z</CreationDate></Bucket>'
|
'2009-02-03T16:45:09.000Z'
|
||||||
% xml_escape(i['name']) for i in containers]))
|
|
||||||
|
body = tostring(elem, xml_declaration=True, encoding='UTF-8')
|
||||||
|
|
||||||
return HTTPOk(content_type='application/xml', body=body)
|
return HTTPOk(content_type='application/xml', body=body)
|
||||||
|
|
||||||
|
|
||||||
@ -481,37 +418,36 @@ class BucketController(Controller):
|
|||||||
return get_err_response('InternalError')
|
return get_err_response('InternalError')
|
||||||
|
|
||||||
objects = loads(resp.body)
|
objects = loads(resp.body)
|
||||||
body = ('<?xml version="1.0" encoding="UTF-8"?>'
|
|
||||||
'<ListBucketResult '
|
elem = Element('ListBucketResult')
|
||||||
'xmlns="http://s3.amazonaws.com/doc/2006-03-01">'
|
SubElement(elem, 'Prefix').text = req.params.get('prefix')
|
||||||
'<Prefix>%s</Prefix>'
|
SubElement(elem, 'Marker').text = req.params.get('marker')
|
||||||
'<Marker>%s</Marker>'
|
SubElement(elem, 'Delimiter').text = req.params.get('delimiter')
|
||||||
'<Delimiter>%s</Delimiter>'
|
if max_keys > 0 and len(objects) == max_keys + 1:
|
||||||
'<IsTruncated>%s</IsTruncated>'
|
is_truncated = 'true'
|
||||||
'<MaxKeys>%s</MaxKeys>'
|
else:
|
||||||
'<Name>%s</Name>'
|
is_truncated = 'false'
|
||||||
'%s'
|
SubElement(elem, 'IsTruncated').text = is_truncated
|
||||||
'%s'
|
SubElement(elem, 'MaxKeys').text = str(max_keys)
|
||||||
'</ListBucketResult>' %
|
SubElement(elem, 'Name').text = self.container_name
|
||||||
(
|
|
||||||
xml_escape(req.params.get('prefix', '')),
|
for o in objects[:max_keys]:
|
||||||
xml_escape(req.params.get('marker', '')),
|
if 'subdir' not in o:
|
||||||
xml_escape(req.params.get('delimiter', '')),
|
contents = SubElement(elem, 'Contents')
|
||||||
'true' if max_keys > 0 and len(objects) == (max_keys + 1) else
|
SubElement(contents, 'Key').text = o['name']
|
||||||
'false',
|
SubElement(contents, 'LastModified').text = \
|
||||||
max_keys,
|
o['last_modified'] + 'Z'
|
||||||
xml_escape(self.container_name),
|
SubElement(contents, 'ETag').text = o['hash']
|
||||||
"".join(['<Contents><Key>%s</Key><LastModified>%sZ</LastModif'
|
SubElement(contents, 'Size').text = str(o['bytes'])
|
||||||
'ied><ETag>%s</ETag><Size>%s</Size><StorageClass>STA'
|
add_canonical_user(contents, 'Owner', self.account_name)
|
||||||
'NDARD</StorageClass><Owner><ID>%s</ID><DisplayName>'
|
|
||||||
'%s</DisplayName></Owner></Contents>' %
|
for o in objects[:max_keys]:
|
||||||
(xml_escape(i['name']), i['last_modified'],
|
if 'subdir' in o:
|
||||||
i['hash'],
|
common_prefixes = SubElement(elem, 'CommonPrefixes')
|
||||||
i['bytes'], self.account_name, self.account_name)
|
SubElement(common_prefixes, 'Prefix').text = o['subdir']
|
||||||
for i in objects[:max_keys] if 'subdir' not in i]),
|
|
||||||
"".join(['<CommonPrefixes><Prefix>%s</Prefix></CommonPrefixes>'
|
body = tostring(elem, xml_declaration=True, encoding='UTF-8')
|
||||||
% xml_escape(i['subdir'])
|
|
||||||
for i in objects[:max_keys] if 'subdir' in i])))
|
|
||||||
return HTTPOk(body=body, content_type='application/xml')
|
return HTTPOk(body=body, content_type='application/xml')
|
||||||
|
|
||||||
def PUT(self, req):
|
def PUT(self, req):
|
||||||
@ -662,9 +598,9 @@ class ObjectController(Controller):
|
|||||||
return get_err_response('InternalError')
|
return get_err_response('InternalError')
|
||||||
|
|
||||||
if 'HTTP_X_COPY_FROM' in req.environ:
|
if 'HTTP_X_COPY_FROM' in req.environ:
|
||||||
body = '<CopyObjectResult>' \
|
elem = Element('CopyObjectResult')
|
||||||
'<ETag>"%s"</ETag>' \
|
SubElement(elem, 'ETag').text = '"%s"' % resp.etag
|
||||||
'</CopyObjectResult>' % resp.etag
|
body = tostring(elem, xml_declaration=True, encoding='UTF-8')
|
||||||
return HTTPOk(body=body)
|
return HTTPOk(body=body)
|
||||||
|
|
||||||
return HTTPOk(etag=resp.etag)
|
return HTTPOk(etag=resp.etag)
|
||||||
@ -802,13 +738,11 @@ class LocationController(Controller):
|
|||||||
else:
|
else:
|
||||||
return get_err_response('InternalError')
|
return get_err_response('InternalError')
|
||||||
|
|
||||||
body = ('<?xml version="1.0" encoding="UTF-8"?>'
|
elem = Element('LocationConstraint')
|
||||||
'<LocationConstraint '
|
if self.location != 'US':
|
||||||
'xmlns="http://s3.amazonaws.com/doc/2006-03-01/"')
|
elem.text = self.location
|
||||||
if self.location == 'US':
|
body = tostring(elem, xml_declaration=True, encoding='UTF-8')
|
||||||
body += '/>'
|
|
||||||
else:
|
|
||||||
body += ('>%s</LocationConstraint>' % self.location)
|
|
||||||
return HTTPOk(body=body, content_type='application/xml')
|
return HTTPOk(body=body, content_type='application/xml')
|
||||||
|
|
||||||
|
|
||||||
@ -837,9 +771,9 @@ class LoggingStatusController(Controller):
|
|||||||
return get_err_response('InternalError')
|
return get_err_response('InternalError')
|
||||||
|
|
||||||
# logging disabled
|
# logging disabled
|
||||||
body = ('<?xml version="1.0" encoding="UTF-8"?>'
|
elem = Element('BucketLoggingStatus')
|
||||||
'<BucketLoggingStatus '
|
body = tostring(elem, xml_declaration=True, encoding='UTF-8')
|
||||||
'xmlns="http://doc.s3.amazonaws.com/2006-03-01" />')
|
|
||||||
return HTTPOk(body=body, content_type='application/xml')
|
return HTTPOk(body=body, content_type='application/xml')
|
||||||
|
|
||||||
def PUT(self, req):
|
def PUT(self, req):
|
||||||
@ -859,31 +793,17 @@ class MultiObjectDeleteController(Controller):
|
|||||||
Handles Delete Multiple Objects.
|
Handles Delete Multiple Objects.
|
||||||
"""
|
"""
|
||||||
def object_key_iter(xml):
|
def object_key_iter(xml):
|
||||||
dom = parseString(xml)
|
elem = fromstring(xml)
|
||||||
delete = dom.getElementsByTagName('Delete')[0]
|
for obj in elem.iterchildren('Object'):
|
||||||
for obj in delete.getElementsByTagName('Object'):
|
key = obj.find('./Key').text
|
||||||
key = obj.getElementsByTagName('Key')[0].firstChild.data
|
version = obj.find('./VersionId')
|
||||||
version = None
|
if version is not None:
|
||||||
if obj.getElementsByTagName('VersionId').length > 0:
|
version = version.text
|
||||||
version = obj.getElementsByTagName('VersionId')[0]\
|
|
||||||
.firstChild.data
|
|
||||||
yield (key, version)
|
yield (key, version)
|
||||||
|
|
||||||
def get_deleted_elem(key):
|
elem = Element('DeleteResult')
|
||||||
return ' <Deleted>\r\n' \
|
|
||||||
' <Key>%s</Key>\r\n' \
|
|
||||||
' </Deleted>\r\n' % (key)
|
|
||||||
|
|
||||||
def get_err_elem(key, err_code, message):
|
|
||||||
return ' <Error>\r\n' \
|
|
||||||
' <Key>%s</Key>\r\n' \
|
|
||||||
' <Code>%s</Code>\r\n' \
|
|
||||||
' <Message>%s</Message>\r\n' \
|
|
||||||
' </Error>\r\n' % (key, err_code, message)
|
|
||||||
|
|
||||||
body = '<?xml version="1.0" encoding="UTF-8"?>\r\n' \
|
|
||||||
'<DeleteResult ' \
|
|
||||||
'xmlns="http://doc.s3.amazonaws.com/2006-03-01">\r\n'
|
|
||||||
for key, version in object_key_iter(req.body):
|
for key, version in object_key_iter(req.body):
|
||||||
if version is not None:
|
if version is not None:
|
||||||
# TODO: delete the specific version of the object
|
# TODO: delete the specific version of the object
|
||||||
@ -900,15 +820,20 @@ class MultiObjectDeleteController(Controller):
|
|||||||
status = sub_resp.status_int
|
status = sub_resp.status_int
|
||||||
|
|
||||||
if status == HTTP_NO_CONTENT or status == HTTP_NOT_FOUND:
|
if status == HTTP_NO_CONTENT or status == HTTP_NOT_FOUND:
|
||||||
body += get_deleted_elem(key)
|
deleted = SubElement(elem, 'Deleted')
|
||||||
|
SubElement(deleted, 'Key').text = key
|
||||||
else:
|
else:
|
||||||
|
error = SubElement(elem, 'Error')
|
||||||
|
SubElement(error, 'Key').text = key
|
||||||
if status == HTTP_UNAUTHORIZED:
|
if status == HTTP_UNAUTHORIZED:
|
||||||
body += get_err_elem(key, 'AccessDenied', 'Access Denied')
|
SubElement(error, 'Code').text = 'AccessDenied'
|
||||||
|
SubElement(error, 'Message').text = 'Access Denied'
|
||||||
else:
|
else:
|
||||||
body += get_err_elem(key, 'InternalError',
|
SubElement(error, 'Code').text = 'InternalError'
|
||||||
'Internal Error')
|
SubElement(error, 'Message').text = 'Internal Error'
|
||||||
|
|
||||||
|
body = tostring(elem, xml_declaration=True, encoding='UTF-8')
|
||||||
|
|
||||||
body += '</DeleteResult>\r\n'
|
|
||||||
return HTTPOk(body=body)
|
return HTTPOk(body=body)
|
||||||
|
|
||||||
|
|
||||||
@ -1010,8 +935,9 @@ class VersioningController(Controller):
|
|||||||
return get_err_response('InternalError')
|
return get_err_response('InternalError')
|
||||||
|
|
||||||
# Just report there is no versioning configured here.
|
# Just report there is no versioning configured here.
|
||||||
body = ('<VersioningConfiguration '
|
elem = Element('VersioningConfiguration')
|
||||||
'xmlns="http://s3.amazonaws.com/doc/2006-03-01/"/>')
|
body = tostring(elem, xml_declaration=True, encoding='UTF-8')
|
||||||
|
|
||||||
return HTTPOk(body=body, content_type="text/plain")
|
return HTTPOk(body=body, content_type="text/plain")
|
||||||
|
|
||||||
def PUT(self, req):
|
def PUT(self, req):
|
||||||
|
@ -20,7 +20,7 @@ import hashlib
|
|||||||
import base64
|
import base64
|
||||||
from urllib import unquote, quote
|
from urllib import unquote, quote
|
||||||
|
|
||||||
import xml.dom.minidom
|
from lxml.etree import fromstring, tostring, Element, SubElement
|
||||||
import simplejson
|
import simplejson
|
||||||
|
|
||||||
from swift.common import swob
|
from swift.common import swob
|
||||||
@ -78,15 +78,12 @@ class TestSwift3(unittest.TestCase):
|
|||||||
('with space', '2011-01-05T02:19:14.275290', 0, 390),
|
('with space', '2011-01-05T02:19:14.275290', 0, 390),
|
||||||
('with%20space', '2011-01-05T02:19:14.275290', 0, 390))
|
('with%20space', '2011-01-05T02:19:14.275290', 0, 390))
|
||||||
|
|
||||||
json_pattern = ['"name":%s', '"last_modified":%s', '"hash":%s',
|
json_pattern = ['"name":"%s"', '"last_modified":"%s"', '"hash":"%s"',
|
||||||
'"bytes":%s']
|
'"bytes":%s']
|
||||||
json_pattern = '{' + ','.join(json_pattern) + '}'
|
json_pattern = '{' + ','.join(json_pattern) + '}'
|
||||||
json_out = []
|
json_out = []
|
||||||
for b in self.objects:
|
for b in self.objects:
|
||||||
name = simplejson.dumps(b[0])
|
json_out.append(json_pattern % b)
|
||||||
time = simplejson.dumps(b[1])
|
|
||||||
json_out.append(json_pattern %
|
|
||||||
(name, time, b[2], b[3]))
|
|
||||||
object_list = '[' + ','.join(json_out) + ']'
|
object_list = '[' + ','.join(json_out) + ']'
|
||||||
self.swift.register('GET', '/v1/AUTH_test/junk', swob.HTTPOk, {},
|
self.swift.register('GET', '/v1/AUTH_test/junk', swob.HTTPOk, {},
|
||||||
object_list)
|
object_list)
|
||||||
@ -121,6 +118,11 @@ class TestSwift3(unittest.TestCase):
|
|||||||
self.swift.register('DELETE', '/v1/AUTH_test/bucket/object',
|
self.swift.register('DELETE', '/v1/AUTH_test/bucket/object',
|
||||||
swob.HTTPNoContent, {}, None)
|
swob.HTTPNoContent, {}, None)
|
||||||
|
|
||||||
|
def _get_error_code(self, body):
|
||||||
|
elem = fromstring(body)
|
||||||
|
self.assertEquals(elem.tag, 'Error')
|
||||||
|
return elem.find('./Code').text
|
||||||
|
|
||||||
def call_app(self, req, app=None, expect_exception=False):
|
def call_app(self, req, app=None, expect_exception=False):
|
||||||
if app is None:
|
if app is None:
|
||||||
app = self.app
|
app = self.app
|
||||||
@ -163,20 +165,14 @@ class TestSwift3(unittest.TestCase):
|
|||||||
req = Request.blank('/something',
|
req = Request.blank('/something',
|
||||||
headers={'Authorization': 'hoge'})
|
headers={'Authorization': 'hoge'})
|
||||||
status, headers, body = self.call_swift3(req)
|
status, headers, body = self.call_swift3(req)
|
||||||
dom = xml.dom.minidom.parseString(body)
|
self.assertEquals(self._get_error_code(body), 'AccessDenied')
|
||||||
self.assertEquals(dom.firstChild.nodeName, 'Error')
|
|
||||||
code = dom.getElementsByTagName('Code')[0].childNodes[0].nodeValue
|
|
||||||
self.assertEquals(code, 'AccessDenied')
|
|
||||||
|
|
||||||
def test_bad_method(self):
|
def test_bad_method(self):
|
||||||
req = Request.blank('/',
|
req = Request.blank('/',
|
||||||
environ={'REQUEST_METHOD': 'PUT'},
|
environ={'REQUEST_METHOD': 'PUT'},
|
||||||
headers={'Authorization': 'AWS test:tester:hmac'})
|
headers={'Authorization': 'AWS test:tester:hmac'})
|
||||||
status, headers, body = self.call_swift3(req)
|
status, headers, body = self.call_swift3(req)
|
||||||
dom = xml.dom.minidom.parseString(body)
|
self.assertEquals(self._get_error_code(body), 'MethodNotAllowed')
|
||||||
self.assertEquals(dom.firstChild.nodeName, 'Error')
|
|
||||||
code = dom.getElementsByTagName('Code')[0].childNodes[0].nodeValue
|
|
||||||
self.assertEquals(code, 'MethodNotAllowed')
|
|
||||||
|
|
||||||
def test_path_info_encode(self):
|
def test_path_info_encode(self):
|
||||||
bucket_name = 'b%75cket'
|
bucket_name = 'b%75cket'
|
||||||
@ -199,9 +195,7 @@ class TestSwift3(unittest.TestCase):
|
|||||||
req = Request.blank(path, environ={'REQUEST_METHOD': method},
|
req = Request.blank(path, environ={'REQUEST_METHOD': method},
|
||||||
headers=headers)
|
headers=headers)
|
||||||
status, headers, body = self.call_swift3(req)
|
status, headers, body = self.call_swift3(req)
|
||||||
dom = xml.dom.minidom.parseString(body)
|
return self._get_error_code(body)
|
||||||
self.assertEquals(dom.firstChild.nodeName, 'Error')
|
|
||||||
return dom.getElementsByTagName('Code')[0].childNodes[0].nodeValue
|
|
||||||
|
|
||||||
def test_service_GET_error(self):
|
def test_service_GET_error(self):
|
||||||
code = self._test_method_error('GET', '', swob.HTTPUnauthorized)
|
code = self._test_method_error('GET', '', swob.HTTPUnauthorized)
|
||||||
@ -218,17 +212,17 @@ class TestSwift3(unittest.TestCase):
|
|||||||
status, headers, body = self.call_swift3(req)
|
status, headers, body = self.call_swift3(req)
|
||||||
self.assertEquals(status.split()[0], '200')
|
self.assertEquals(status.split()[0], '200')
|
||||||
|
|
||||||
dom = xml.dom.minidom.parseString(body)
|
elem = fromstring(body)
|
||||||
self.assertEquals(dom.firstChild.nodeName, 'ListAllMyBucketsResult')
|
self.assertEquals(elem.tag, 'ListAllMyBucketsResult')
|
||||||
|
|
||||||
buckets = [n for n in dom.getElementsByTagName('Bucket')]
|
all_buckets = elem.find('./Buckets')
|
||||||
listing = [n for n in buckets[0].childNodes if n.nodeName != '#text']
|
buckets = all_buckets.iterchildren('Bucket')
|
||||||
|
listing = list(list(buckets)[0])
|
||||||
self.assertEquals(len(listing), 2)
|
self.assertEquals(len(listing), 2)
|
||||||
|
|
||||||
names = []
|
names = []
|
||||||
for b in buckets:
|
for b in all_buckets.iterchildren('Bucket'):
|
||||||
if b.childNodes[0].nodeName == 'Name':
|
names.append(b.find('./Name').text)
|
||||||
names.append(b.childNodes[0].childNodes[0].nodeValue)
|
|
||||||
|
|
||||||
self.assertEquals(len(names), len(self.buckets))
|
self.assertEquals(len(names), len(self.buckets))
|
||||||
for i in self.buckets:
|
for i in self.buckets:
|
||||||
@ -252,20 +246,17 @@ class TestSwift3(unittest.TestCase):
|
|||||||
status, headers, body = self.call_swift3(req)
|
status, headers, body = self.call_swift3(req)
|
||||||
self.assertEquals(status.split()[0], '200')
|
self.assertEquals(status.split()[0], '200')
|
||||||
|
|
||||||
dom = xml.dom.minidom.parseString(body)
|
elem = fromstring(body)
|
||||||
self.assertEquals(dom.firstChild.nodeName, 'ListBucketResult')
|
self.assertEquals(elem.tag, 'ListBucketResult')
|
||||||
name = dom.getElementsByTagName('Name')[0].childNodes[0].nodeValue
|
name = elem.find('./Name').text
|
||||||
self.assertEquals(name, bucket_name)
|
self.assertEquals(name, bucket_name)
|
||||||
|
|
||||||
objects = [n for n in dom.getElementsByTagName('Contents')]
|
objects = elem.iterchildren('Contents')
|
||||||
|
|
||||||
names = []
|
names = []
|
||||||
for o in objects:
|
for o in objects:
|
||||||
if o.childNodes[0].nodeName == 'Key':
|
names.append(o.find('./Key').text)
|
||||||
names.append(o.childNodes[0].childNodes[0].nodeValue)
|
self.assertTrue(o.find('./LastModified').text.endswith('Z'))
|
||||||
if o.childNodes[1].nodeName == 'LastModified':
|
|
||||||
self.assertTrue(
|
|
||||||
o.childNodes[1].childNodes[0].nodeValue.endswith('Z'))
|
|
||||||
|
|
||||||
self.assertEquals(len(names), len(self.objects))
|
self.assertEquals(len(names), len(self.objects))
|
||||||
for i in self.objects:
|
for i in self.objects:
|
||||||
@ -279,18 +270,16 @@ class TestSwift3(unittest.TestCase):
|
|||||||
'QUERY_STRING': 'max-keys=5'},
|
'QUERY_STRING': 'max-keys=5'},
|
||||||
headers={'Authorization': 'AWS test:tester:hmac'})
|
headers={'Authorization': 'AWS test:tester:hmac'})
|
||||||
status, headers, body = self.call_swift3(req)
|
status, headers, body = self.call_swift3(req)
|
||||||
dom = xml.dom.minidom.parseString(body)
|
elem = fromstring(body)
|
||||||
self.assertEquals(dom.getElementsByTagName('IsTruncated')[0].
|
self.assertEquals(elem.find('./IsTruncated').text, 'false')
|
||||||
childNodes[0].nodeValue, 'false')
|
|
||||||
|
|
||||||
req = Request.blank('/%s' % bucket_name,
|
req = Request.blank('/%s' % bucket_name,
|
||||||
environ={'REQUEST_METHOD': 'GET',
|
environ={'REQUEST_METHOD': 'GET',
|
||||||
'QUERY_STRING': 'max-keys=4'},
|
'QUERY_STRING': 'max-keys=4'},
|
||||||
headers={'Authorization': 'AWS test:tester:hmac'})
|
headers={'Authorization': 'AWS test:tester:hmac'})
|
||||||
status, headers, body = self.call_swift3(req)
|
status, headers, body = self.call_swift3(req)
|
||||||
dom = xml.dom.minidom.parseString(body)
|
elem = fromstring(body)
|
||||||
self.assertEquals(dom.getElementsByTagName('IsTruncated')[0].
|
self.assertEquals(elem.find('./IsTruncated').text, 'true')
|
||||||
childNodes[0].nodeValue, 'true')
|
|
||||||
|
|
||||||
def test_bucket_GET_max_keys(self):
|
def test_bucket_GET_max_keys(self):
|
||||||
bucket_name = 'junk'
|
bucket_name = 'junk'
|
||||||
@ -300,9 +289,8 @@ class TestSwift3(unittest.TestCase):
|
|||||||
'QUERY_STRING': 'max-keys=5'},
|
'QUERY_STRING': 'max-keys=5'},
|
||||||
headers={'Authorization': 'AWS test:tester:hmac'})
|
headers={'Authorization': 'AWS test:tester:hmac'})
|
||||||
status, headers, body = self.call_swift3(req)
|
status, headers, body = self.call_swift3(req)
|
||||||
dom = xml.dom.minidom.parseString(body)
|
elem = fromstring(body)
|
||||||
self.assertEquals(dom.getElementsByTagName('MaxKeys')[0].
|
self.assertEquals(elem.find('./MaxKeys').text, '5')
|
||||||
childNodes[0].nodeValue, '5')
|
|
||||||
_, path = self.swift.calls[-1]
|
_, path = self.swift.calls[-1]
|
||||||
_, query_string = path.split('?')
|
_, query_string = path.split('?')
|
||||||
args = dict(cgi.parse_qsl(query_string))
|
args = dict(cgi.parse_qsl(query_string))
|
||||||
@ -313,9 +301,8 @@ class TestSwift3(unittest.TestCase):
|
|||||||
'QUERY_STRING': 'max-keys=5000'},
|
'QUERY_STRING': 'max-keys=5000'},
|
||||||
headers={'Authorization': 'AWS test:tester:hmac'})
|
headers={'Authorization': 'AWS test:tester:hmac'})
|
||||||
status, headers, body = self.call_swift3(req)
|
status, headers, body = self.call_swift3(req)
|
||||||
dom = xml.dom.minidom.parseString(body)
|
elem = fromstring(body)
|
||||||
self.assertEquals(dom.getElementsByTagName('MaxKeys')[0].
|
self.assertEquals(elem.find('./MaxKeys').text, '1000')
|
||||||
childNodes[0].nodeValue, '1000')
|
|
||||||
_, path = self.swift.calls[-1]
|
_, path = self.swift.calls[-1]
|
||||||
_, query_string = path.split('?')
|
_, query_string = path.split('?')
|
||||||
args = dict(cgi.parse_qsl(query_string))
|
args = dict(cgi.parse_qsl(query_string))
|
||||||
@ -328,13 +315,10 @@ class TestSwift3(unittest.TestCase):
|
|||||||
'delimiter=a&marker=b&prefix=c'},
|
'delimiter=a&marker=b&prefix=c'},
|
||||||
headers={'Authorization': 'AWS test:tester:hmac'})
|
headers={'Authorization': 'AWS test:tester:hmac'})
|
||||||
status, headers, body = self.call_swift3(req)
|
status, headers, body = self.call_swift3(req)
|
||||||
dom = xml.dom.minidom.parseString(body)
|
elem = fromstring(body)
|
||||||
self.assertEquals(dom.getElementsByTagName('Prefix')[0].
|
self.assertEquals(elem.find('./Prefix').text, 'c')
|
||||||
childNodes[0].nodeValue, 'c')
|
self.assertEquals(elem.find('./Marker').text, 'b')
|
||||||
self.assertEquals(dom.getElementsByTagName('Marker')[0].
|
self.assertEquals(elem.find('./Delimiter').text, 'a')
|
||||||
childNodes[0].nodeValue, 'b')
|
|
||||||
self.assertEquals(dom.getElementsByTagName('Delimiter')[0].
|
|
||||||
childNodes[0].nodeValue, 'a')
|
|
||||||
_, path = self.swift.calls[-1]
|
_, path = self.swift.calls[-1]
|
||||||
_, query_string = path.split('?')
|
_, query_string = path.split('?')
|
||||||
args = dict(cgi.parse_qsl(query_string))
|
args = dict(cgi.parse_qsl(query_string))
|
||||||
@ -386,13 +370,12 @@ class TestSwift3(unittest.TestCase):
|
|||||||
status, headers, body = self.call_swift3(req)
|
status, headers, body = self.call_swift3(req)
|
||||||
self.assertEquals(status.split()[0], '204')
|
self.assertEquals(status.split()[0], '204')
|
||||||
|
|
||||||
def _check_acl(self, owner, resp):
|
def _check_acl(self, owner, body):
|
||||||
dom = xml.dom.minidom.parseString("".join(resp))
|
elem = fromstring(body)
|
||||||
self.assertEquals(dom.firstChild.nodeName, 'AccessControlPolicy')
|
self.assertEquals(elem.tag, 'AccessControlPolicy')
|
||||||
permission = dom.getElementsByTagName('Permission')[0]
|
permission = elem.find('./AccessControlList/Grant/Permission').text
|
||||||
name = permission.childNodes[0].nodeValue
|
self.assertEquals(permission, 'FULL_CONTROL')
|
||||||
self.assertEquals(name, 'FULL_CONTROL')
|
name = elem.find('./AccessControlList/Grant/Grantee/ID').text
|
||||||
name = dom.getElementsByTagName('ID')[0].childNodes[0].nodeValue
|
|
||||||
self.assertEquals(name, owner)
|
self.assertEquals(name, owner)
|
||||||
|
|
||||||
def test_bucket_acl_GET(self):
|
def test_bucket_acl_GET(self):
|
||||||
@ -409,8 +392,8 @@ class TestSwift3(unittest.TestCase):
|
|||||||
environ={'REQUEST_METHOD': 'GET'},
|
environ={'REQUEST_METHOD': 'GET'},
|
||||||
headers={'Authorization': 'AWS test:tester:hmac'})
|
headers={'Authorization': 'AWS test:tester:hmac'})
|
||||||
status, headers, body = self.call_swift3(req)
|
status, headers, body = self.call_swift3(req)
|
||||||
dom = xml.dom.minidom.parseString(body)
|
elem = fromstring(body)
|
||||||
self.assertEquals(dom.firstChild.nodeName, 'VersioningConfiguration')
|
self.assertEquals(elem.tag, 'VersioningConfiguration')
|
||||||
|
|
||||||
def _test_object_GETorHEAD(self, method):
|
def _test_object_GETorHEAD(self, method):
|
||||||
req = Request.blank('/bucket/object',
|
req = Request.blank('/bucket/object',
|
||||||
@ -535,15 +518,12 @@ class TestSwift3(unittest.TestCase):
|
|||||||
self.assertEquals(status.split()[0], '204')
|
self.assertEquals(status.split()[0], '204')
|
||||||
|
|
||||||
def test_object_multi_DELETE(self):
|
def test_object_multi_DELETE(self):
|
||||||
body = '<?xml version="1.0" encoding="UTF-8"?> \
|
elem = Element('Delete')
|
||||||
<Delete>\
|
for key in ['Key1', 'Key2']:
|
||||||
<Object>\
|
obj = SubElement(elem, 'Object')
|
||||||
<Key>Key1</Key>\
|
SubElement(obj, 'Key').text = key
|
||||||
</Object>\
|
body = tostring(elem, xml_declaration=True, encoding='UTF-8')
|
||||||
<Object>\
|
|
||||||
<Key>Key2</Key>\
|
|
||||||
</Object>\
|
|
||||||
</Delete>'
|
|
||||||
req = Request.blank('/bucket?delete',
|
req = Request.blank('/bucket?delete',
|
||||||
environ={'REQUEST_METHOD': 'POST'},
|
environ={'REQUEST_METHOD': 'POST'},
|
||||||
headers={'Authorization': 'AWS test:tester:hmac'},
|
headers={'Authorization': 'AWS test:tester:hmac'},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user