Make symlink work with Unicode account names

Also, ensure that the stored symlink headers really *are* url-encoded as
we've been assuming.

Change-Id: I1f300d69bec43f0deb430294da05a4ec04308040
Related-Bug: 1774238
Closes-Bug: #1821240
This commit is contained in:
Tim Burke 2018-06-01 15:38:10 -07:00 committed by Clay Gerrard
parent 65660faf69
commit e5e22ebeba
3 changed files with 42 additions and 16 deletions

View File

@ -161,7 +161,7 @@ from cgi import parse_header
from six.moves.urllib.parse import unquote from six.moves.urllib.parse import unquote
from swift.common.utils import get_logger, register_swift_info, split_path, \ from swift.common.utils import get_logger, register_swift_info, split_path, \
MD5_OF_EMPTY_STRING, closing_if_possible MD5_OF_EMPTY_STRING, closing_if_possible, quote
from swift.common.constraints import check_account_format from swift.common.constraints import check_account_format
from swift.common.wsgi import WSGIContext, make_subrequest from swift.common.wsgi import WSGIContext, make_subrequest
from swift.common.request_helpers import get_sys_meta_prefix, \ from swift.common.request_helpers import get_sys_meta_prefix, \
@ -208,6 +208,7 @@ def _check_symlink_header(req):
req, TGT_OBJ_SYMLINK_HDR, 2, req, TGT_OBJ_SYMLINK_HDR, 2,
'X-Symlink-Target header must be of the ' 'X-Symlink-Target header must be of the '
'form <container name>/<object name>') 'form <container name>/<object name>')
req.headers[TGT_OBJ_SYMLINK_HDR] = quote('%s/%s' % (container, obj))
# Check account format if it exists # Check account format if it exists
account = check_account_format( account = check_account_format(
@ -217,7 +218,9 @@ def _check_symlink_header(req):
# Extract request path # Extract request path
_junk, req_acc, req_cont, req_obj = req.split_path(4, 4, True) _junk, req_acc, req_cont, req_obj = req.split_path(4, 4, True)
if not account: if account:
req.headers[TGT_ACCT_SYMLINK_HDR] = quote(account)
else:
account = req_acc account = req_acc
# Check if symlink targets the symlink itself or not # Check if symlink targets the symlink itself or not
@ -378,9 +381,9 @@ class SymlinkObjectContext(WSGIContext):
:returns: new request for target path if it's symlink otherwise :returns: new request for target path if it's symlink otherwise
None None
""" """
version, account, _junk = split_path(req.path, 2, 3, True) version, account, _junk = req.split_path(2, 3, True)
account = self._response_header_value( account = self._response_header_value(
TGT_ACCT_SYSMETA_SYMLINK_HDR) or account TGT_ACCT_SYSMETA_SYMLINK_HDR) or quote(account)
target_path = os.path.join( target_path = os.path.join(
'/', version, account, '/', version, account,
symlink_target.lstrip('/')) symlink_target.lstrip('/'))
@ -485,7 +488,7 @@ class SymlinkObjectContext(WSGIContext):
if tgt_co: if tgt_co:
version, account, _junk = req.split_path(2, 3, True) version, account, _junk = req.split_path(2, 3, True)
target_acc = self._response_header_value( target_acc = self._response_header_value(
TGT_ACCT_SYSMETA_SYMLINK_HDR) or account TGT_ACCT_SYSMETA_SYMLINK_HDR) or quote(account)
location_hdr = os.path.join( location_hdr = os.path.join(
'/', version, target_acc, tgt_co) '/', version, target_acc, tgt_co)
req.environ['swift.leave_relative_location'] = True req.environ['swift.leave_relative_location'] = True

View File

@ -270,23 +270,45 @@ class TestSymlink(Base):
target_obj = 'dealde%2Fl04 011e%204c8df/flash.png' target_obj = 'dealde%2Fl04 011e%204c8df/flash.png'
link_obj = uuid4().hex link_obj = uuid4().hex
# Now let's write a new target object and symlink will be able to # create target using unnormalized path
# return object
resp = retry( resp = retry(
self._make_request, method='PUT', container=self.env.tgt_cont, self._make_request, method='PUT', container=self.env.tgt_cont,
obj=target_obj, body=TARGET_BODY) obj=target_obj, body=TARGET_BODY)
self.assertEqual(resp.status, 201) self.assertEqual(resp.status, 201)
# you can get it using either name
resp = retry(
self._make_request, method='GET', container=self.env.tgt_cont,
obj=target_obj)
self.assertEqual(resp.status, 200)
self.assertEqual(resp.content, TARGET_BODY)
normalized_quoted_obj = 'dealde/l04%20011e%204c8df/flash.png'
self.assertEqual(normalized_quoted_obj, urllib.parse.quote(
urllib.parse.unquote(target_obj)))
resp = retry(
self._make_request, method='GET', container=self.env.tgt_cont,
obj=normalized_quoted_obj)
self.assertEqual(resp.status, 200)
self.assertEqual(resp.content, TARGET_BODY)
# PUT symlink # create a symlink using the un-normalized target path
self._test_put_symlink(link_cont=self.env.link_cont, link_obj=link_obj, self._test_put_symlink(link_cont=self.env.link_cont, link_obj=link_obj,
tgt_cont=self.env.tgt_cont, tgt_cont=self.env.tgt_cont,
tgt_obj=target_obj) tgt_obj=target_obj)
# and it's normalized
self._assertSymlink( self._assertSymlink(
self.env.link_cont, link_obj, self.env.link_cont, link_obj,
expected_content_location="%s/%s" % (self.env.tgt_cont, expected_content_location='%s/%s' % (
target_obj)) self.env.tgt_cont, normalized_quoted_obj))
# create a symlink using the normalized target path
self._test_put_symlink(link_cont=self.env.link_cont, link_obj=link_obj,
tgt_cont=self.env.tgt_cont,
tgt_obj=normalized_quoted_obj)
# and it's ALSO normalized
self._assertSymlink(
self.env.link_cont, link_obj,
expected_content_location='%s/%s' % (
self.env.tgt_cont, normalized_quoted_obj))
def test_symlink_put_head_get(self): def test_symlink_put_head_get(self):
link_obj = uuid4().hex link_obj = uuid4().hex

View File

@ -18,7 +18,7 @@ from copy import deepcopy
import json import json
import time import time
import unittest2 import unittest2
from six.moves.urllib.parse import quote from six.moves.urllib.parse import quote, unquote
import test.functional as tf import test.functional as tf
@ -652,7 +652,7 @@ class TestObjectVersioning(Base):
tgt_b.write("bbbbb") tgt_b.write("bbbbb")
symlink_name = Utils.create_name() symlink_name = Utils.create_name()
sym_tgt_header = '%s/%s' % (container.name, tgt_a_name) sym_tgt_header = quote(unquote('%s/%s' % (container.name, tgt_a_name)))
sym_headers_a = {'X-Symlink-Target': sym_tgt_header} sym_headers_a = {'X-Symlink-Target': sym_tgt_header}
symlink = container.file(symlink_name) symlink = container.file(symlink_name)
symlink.write("", hdrs=sym_headers_a) symlink.write("", hdrs=sym_headers_a)
@ -684,8 +684,9 @@ class TestObjectVersioning(Base):
sym_info = symlink.info(parms={'symlink': 'get'}) sym_info = symlink.info(parms={'symlink': 'get'})
self.assertEqual("aaaaa", symlink.read()) self.assertEqual("aaaaa", symlink.read())
self.assertEqual(MD5_OF_EMPTY_STRING, sym_info['etag']) self.assertEqual(MD5_OF_EMPTY_STRING, sym_info['etag'])
self.assertEqual('%s/%s' % (self.env.container.name, target.name), self.assertEqual(
sym_info['x_symlink_target']) quote(unquote('%s/%s' % (self.env.container.name, target.name))),
sym_info['x_symlink_target'])
def _setup_symlink(self): def _setup_symlink(self):
target = self.env.container.file('target-object') target = self.env.container.file('target-object')