diff --git a/swift/common/middleware/symlink.py b/swift/common/middleware/symlink.py index 4e131de452..d2c6444386 100644 --- a/swift/common/middleware/symlink.py +++ b/swift/common/middleware/symlink.py @@ -211,7 +211,7 @@ from swift.common.request_helpers import get_sys_meta_prefix, \ update_ignore_range_header from swift.common.swob import Request, HTTPBadRequest, HTTPTemporaryRedirect, \ HTTPException, HTTPConflict, HTTPPreconditionFailed, wsgi_quote, \ - wsgi_unquote, status_map + wsgi_unquote, status_map, normalize_etag from swift.common.http import is_success, HTTP_NOT_FOUND from swift.common.exceptions import LinkIterError from swift.common.header_key_dict import HeaderKeyDict @@ -285,7 +285,7 @@ def _validate_and_prep_request_headers(req): raise HTTPBadRequest( body='Symlink cannot target itself', request=req, content_type='text/plain') - etag = req.headers.get(TGT_ETAG_SYMLINK_HDR, None) + etag = normalize_etag(req.headers.get(TGT_ETAG_SYMLINK_HDR, None)) if etag and any(c in etag for c in ';"\\'): # See cgi.parse_header for why the above chars are problematic raise HTTPBadRequest( diff --git a/test/functional/test_symlink.py b/test/functional/test_symlink.py index 0a58faca46..5cd66d510c 100755 --- a/test/functional/test_symlink.py +++ b/test/functional/test_symlink.py @@ -1562,7 +1562,7 @@ class TestSymlinkSlo(Base): self.env.container2.name, 'manifest-abcde'), 'X-Symlink-Target-Etag': slo_etag, }) - self.assert_status(400) # no quotes allowed! + self.assert_status(409) # quotes OK, but doesn't match # try the slo etag w/o the quotes slo_etag = slo_etag.strip('"') @@ -1571,7 +1571,7 @@ class TestSymlinkSlo(Base): self.env.container2.name, 'manifest-abcde'), 'X-Symlink-Target-Etag': slo_etag, }) - self.assert_status(409) # that just doesn't match + self.assert_status(409) # that still doesn't match def test_static_link_target_symlink_to_slo_manifest(self): # write symlink diff --git a/test/unit/common/middleware/test_symlink.py b/test/unit/common/middleware/test_symlink.py index e983bacbf7..01875ba48b 100644 --- a/test/unit/common/middleware/test_symlink.py +++ b/test/unit/common/middleware/test_symlink.py @@ -144,6 +144,33 @@ class TestSymlinkMiddleware(TestSymlinkMiddlewareBase): self.assertEqual('application/foo', self.app._calls[-1].headers['Content-Type']) + def test_symlink_simple_put_with_quoted_etag(self): + self.app.register('HEAD', '/v1/a/c1/o', swob.HTTPOk, { + 'Etag': 'tgt-etag', 'Content-Length': 42, + 'Content-Type': 'application/foo'}) + self.app.register('PUT', '/v1/a/c/symlink', swob.HTTPCreated, {}) + req = Request.blank('/v1/a/c/symlink', method='PUT', + headers={ + 'X-Symlink-Target': 'c1/o', + 'X-Symlink-Target-Etag': '"tgt-etag"', + }, body='') + status, headers, body = self.call_sym(req) + self.assertEqual(status, '201 Created') + method, path, hdrs = self.app.calls_with_headers[1] + val = hdrs.get('X-Object-Sysmeta-Symlink-Target') + self.assertEqual(val, 'c1/o') + self.assertNotIn('X-Object-Sysmeta-Symlink-Target-Account', hdrs) + val = hdrs.get('X-Object-Sysmeta-Container-Update-Override-Etag') + self.assertEqual(val, '%s; symlink_target=c1/o; ' + 'symlink_target_etag=tgt-etag; ' + 'symlink_target_bytes=42' % MD5_OF_EMPTY_STRING) + self.assertEqual([ + ('HEAD', '/v1/a/c1/o'), + ('PUT', '/v1/a/c/symlink'), + ], self.app.calls) + self.assertEqual('application/foo', + self.app._calls[-1].headers['Content-Type']) + def test_symlink_simple_put_with_etag_target_missing_content_type(self): self.app.register('HEAD', '/v1/a/c1/o', swob.HTTPOk, { 'Etag': 'tgt-etag', 'Content-Length': 42}) @@ -1094,15 +1121,14 @@ class SymlinkCopyingTestCase(TestSymlinkMiddlewareBase): }, body='') status, headers, body = self.call_sym(req) self.assertEqual(status, '409 Conflict') - # the quoted slo-etag is just straight up invalid + # the quoted slo-etag is tolerated, but still doesn't match req = Request.blank('/v1/a/c/symlink', method='PUT', headers={ 'X-Symlink-Target': 'c1/o', 'X-Symlink-Target-Etag': '"slo-etag"', }, body='') status, headers, body = self.call_sym(req) - self.assertEqual(status, '400 Bad Request') - self.assertEqual(b'Bad X-Symlink-Target-Etag format', body) + self.assertEqual(status, '409 Conflict') class SymlinkVersioningTestCase(TestSymlinkMiddlewareBase):