Allow copying of null version
Even though we don't support versioning yet, we can at least tolerate a client that explicitly requests a null versionId The syntax is described at http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectCOPY.html Change-Id: Iedd8cc1b0c6f3a770f28be74eafc58bbef0259ad
This commit is contained in:
parent
8019c2234c
commit
7ff06d58fd
@ -19,8 +19,9 @@ from hashlib import sha1, sha256, md5
|
||||
import hmac
|
||||
import re
|
||||
import six
|
||||
# pylint: disable-msg=import-error
|
||||
from six.moves.urllib.parse import quote, unquote, parse_qsl
|
||||
import string
|
||||
from urllib import quote, unquote
|
||||
|
||||
from swift.common.utils import split_path
|
||||
from swift.common import swob
|
||||
@ -728,13 +729,30 @@ class Request(swob.Request):
|
||||
|
||||
:returns: the source HEAD response
|
||||
"""
|
||||
if 'X-Amz-Copy-Source' not in self.headers:
|
||||
try:
|
||||
src_path = self.headers['X-Amz-Copy-Source']
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
src_path = unquote(self.headers['X-Amz-Copy-Source'])
|
||||
src_path = src_path if src_path.startswith('/') else \
|
||||
('/' + src_path)
|
||||
if '?' in src_path:
|
||||
src_path, qs = src_path.split('?', 1)
|
||||
query = parse_qsl(qs, True)
|
||||
if not query:
|
||||
pass # ignore it
|
||||
elif len(query) > 1 or query[0][0] != 'versionId':
|
||||
raise InvalidArgument('X-Amz-Copy-Source',
|
||||
self.headers['X-Amz-Copy-Source'],
|
||||
'Unsupported copy source parameter.')
|
||||
elif query[0][1] != 'null':
|
||||
# TODO: once we support versioning, we'll need to translate
|
||||
# src_path to the proper location in the versions container
|
||||
raise S3NotImplemented('Versioning is not yet supported')
|
||||
self.headers['X-Amz-Copy-Source'] = src_path
|
||||
|
||||
src_path = unquote(src_path)
|
||||
src_path = src_path if src_path.startswith('/') else ('/' + src_path)
|
||||
src_bucket, src_obj = split_path(src_path, 0, 2, True)
|
||||
|
||||
headers = swob.HeaderKeyDict()
|
||||
headers.update(self._copy_source_headers())
|
||||
|
||||
|
@ -370,6 +370,41 @@ class TestSwift3Object(Swift3FunctionalTestCase):
|
||||
self.assertCommonResponseHeaders(headers)
|
||||
self._assertObjectEtag(self.bucket, obj, etag)
|
||||
|
||||
def test_put_object_copy_source_params(self):
|
||||
obj = 'object'
|
||||
src_headers = {'X-Amz-Meta-Test': 'src'}
|
||||
src_body = 'some content'
|
||||
dst_bucket = 'dst-bucket'
|
||||
dst_obj = 'dst_object'
|
||||
self.conn.make_request('PUT', self.bucket, obj, src_headers, src_body)
|
||||
self.conn.make_request('PUT', dst_bucket)
|
||||
|
||||
headers = {'X-Amz-Copy-Source': '/%s/%s?nonsense' % (
|
||||
self.bucket, obj)}
|
||||
status, headers, body = \
|
||||
self.conn.make_request('PUT', dst_bucket, dst_obj, headers)
|
||||
self.assertEqual(status, 400)
|
||||
self.assertEqual(get_error_code(body), 'InvalidArgument')
|
||||
|
||||
headers = {'X-Amz-Copy-Source': '/%s/%s?versionId=null&nonsense' % (
|
||||
self.bucket, obj)}
|
||||
status, headers, body = \
|
||||
self.conn.make_request('PUT', dst_bucket, dst_obj, headers)
|
||||
self.assertEqual(status, 400)
|
||||
self.assertEqual(get_error_code(body), 'InvalidArgument')
|
||||
|
||||
headers = {'X-Amz-Copy-Source': '/%s/%s?versionId=null' % (
|
||||
self.bucket, obj)}
|
||||
status, headers, body = \
|
||||
self.conn.make_request('PUT', dst_bucket, dst_obj, headers)
|
||||
self.assertEqual(status, 200)
|
||||
self.assertCommonResponseHeaders(headers)
|
||||
status, headers, body = \
|
||||
self.conn.make_request('GET', dst_bucket, dst_obj)
|
||||
self.assertEqual(status, 200)
|
||||
self.assertEqual(headers['x-amz-meta-test'], 'src')
|
||||
self.assertEqual(body, src_body)
|
||||
|
||||
def test_put_object_copy_source(self):
|
||||
obj = 'object'
|
||||
content = 'abcdefghij'
|
||||
|
@ -417,6 +417,28 @@ class TestSwift3Obj(Swift3TestCase):
|
||||
swob.HTTPCreated,
|
||||
{'X-Amz-Copy-Source': '/bucket/'})
|
||||
self.assertEqual(code, 'InvalidArgument')
|
||||
code = self._test_method_error(
|
||||
'PUT', '/bucket/object',
|
||||
swob.HTTPCreated,
|
||||
{'X-Amz-Copy-Source': '/bucket/src_obj?foo=bar'})
|
||||
self.assertEqual(code, 'InvalidArgument')
|
||||
# adding other query paramerters will cause an error
|
||||
code = self._test_method_error(
|
||||
'PUT', '/bucket/object',
|
||||
swob.HTTPCreated,
|
||||
{'X-Amz-Copy-Source': '/bucket/src_obj?versionId=foo&bar=baz'})
|
||||
self.assertEqual(code, 'InvalidArgument')
|
||||
# ...even versionId appears in the last
|
||||
code = self._test_method_error(
|
||||
'PUT', '/bucket/object',
|
||||
swob.HTTPCreated,
|
||||
{'X-Amz-Copy-Source': '/bucket/src_obj?bar=baz&versionId=foo'})
|
||||
self.assertEqual(code, 'InvalidArgument')
|
||||
code = self._test_method_error(
|
||||
'PUT', '/bucket/object',
|
||||
swob.HTTPCreated,
|
||||
{'X-Amz-Copy-Source': '/bucket/src_obj?versionId=foo'})
|
||||
self.assertEqual(code, 'NotImplemented')
|
||||
code = self._test_method_error(
|
||||
'PUT', '/bucket/object',
|
||||
swob.HTTPCreated,
|
||||
@ -535,46 +557,32 @@ class TestSwift3Obj(Swift3TestCase):
|
||||
|
||||
@s3acl
|
||||
def test_object_PUT_copy(self):
|
||||
date_header = self.get_date_header()
|
||||
timestamp = mktime(date_header)
|
||||
last_modified = S3Timestamp(timestamp).s3xmlformat
|
||||
status, headers, body = self._test_object_PUT_copy(
|
||||
swob.HTTPOk, put_header={'Date': date_header},
|
||||
timestamp=timestamp)
|
||||
self.assertEqual(status.split()[0], '200')
|
||||
self.assertEqual(headers['Content-Type'], 'application/xml')
|
||||
def do_test(src_path=None):
|
||||
date_header = self.get_date_header()
|
||||
timestamp = mktime(date_header)
|
||||
last_modified = S3Timestamp(timestamp).s3xmlformat
|
||||
status, headers, body = self._test_object_PUT_copy(
|
||||
swob.HTTPOk, put_header={'Date': date_header},
|
||||
timestamp=timestamp, src_path=src_path)
|
||||
self.assertEqual(status.split()[0], '200')
|
||||
self.assertEqual(headers['Content-Type'], 'application/xml')
|
||||
|
||||
self.assertTrue(headers.get('etag') is None)
|
||||
self.assertTrue(headers.get('x-amz-meta-something') is None)
|
||||
elem = fromstring(body, 'CopyObjectResult')
|
||||
self.assertEqual(elem.find('LastModified').text, last_modified)
|
||||
self.assertEqual(elem.find('ETag').text, '"%s"' % self.etag)
|
||||
self.assertTrue(headers.get('etag') is None)
|
||||
self.assertTrue(headers.get('x-amz-meta-something') is None)
|
||||
elem = fromstring(body, 'CopyObjectResult')
|
||||
self.assertEqual(elem.find('LastModified').text, last_modified)
|
||||
self.assertEqual(elem.find('ETag').text, '"%s"' % self.etag)
|
||||
|
||||
_, _, headers = self.swift.calls_with_headers[-1]
|
||||
self.assertEqual(headers['X-Copy-From'], '/some/source')
|
||||
self.assertEqual(headers['Content-Length'], '0')
|
||||
_, _, headers = self.swift.calls_with_headers[-1]
|
||||
self.assertEqual(headers['X-Copy-From'], '/some/source')
|
||||
self.assertEqual(headers['Content-Length'], '0')
|
||||
|
||||
@s3acl
|
||||
def test_object_PUT_copy_no_slash(self):
|
||||
date_header = self.get_date_header()
|
||||
timestamp = mktime(date_header)
|
||||
last_modified = S3Timestamp(timestamp).s3xmlformat
|
||||
do_test('/some/source')
|
||||
do_test('/some/source?')
|
||||
do_test('/some/source?versionId=null')
|
||||
# Some clients (like Boto) don't include the leading slash;
|
||||
# AWS seems to tolerate this so we should, too
|
||||
status, headers, body = self._test_object_PUT_copy(
|
||||
swob.HTTPOk, src_path='some/source',
|
||||
put_header={'Date': date_header}, timestamp=timestamp)
|
||||
self.assertEqual(status.split()[0], '200')
|
||||
self.assertEqual(headers['Content-Type'], 'application/xml')
|
||||
self.assertTrue(headers.get('etag') is None)
|
||||
self.assertTrue(headers.get('x-amz-meta-something') is None)
|
||||
elem = fromstring(body, 'CopyObjectResult')
|
||||
self.assertEqual(elem.find('LastModified').text, last_modified)
|
||||
self.assertEqual(elem.find('ETag').text, '"%s"' % self.etag)
|
||||
|
||||
_, _, headers = self.swift.calls_with_headers[-1]
|
||||
self.assertEqual(headers['X-Copy-From'], '/some/source')
|
||||
self.assertEqual(headers['Content-Length'], '0')
|
||||
do_test('some/source')
|
||||
|
||||
@s3acl
|
||||
def test_object_PUT_copy_self(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user