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 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.wsgi import WSGIContext, make_subrequest
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,
'X-Symlink-Target header must be of the '
'form <container name>/<object name>')
req.headers[TGT_OBJ_SYMLINK_HDR] = quote('%s/%s' % (container, obj))
# Check account format if it exists
account = check_account_format(
@ -217,7 +218,9 @@ def _check_symlink_header(req):
# Extract request path
_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
# 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
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(
TGT_ACCT_SYSMETA_SYMLINK_HDR) or account
TGT_ACCT_SYSMETA_SYMLINK_HDR) or quote(account)
target_path = os.path.join(
'/', version, account,
symlink_target.lstrip('/'))
@ -485,7 +488,7 @@ class SymlinkObjectContext(WSGIContext):
if tgt_co:
version, account, _junk = req.split_path(2, 3, True)
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(
'/', version, target_acc, tgt_co)
req.environ['swift.leave_relative_location'] = True

View File

@ -270,23 +270,45 @@ class TestSymlink(Base):
target_obj = 'dealde%2Fl04 011e%204c8df/flash.png'
link_obj = uuid4().hex
# Now let's write a new target object and symlink will be able to
# return object
# create target using unnormalized path
resp = retry(
self._make_request, method='PUT', container=self.env.tgt_cont,
obj=target_obj, body=TARGET_BODY)
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,
tgt_cont=self.env.tgt_cont,
tgt_obj=target_obj)
# and it's normalized
self._assertSymlink(
self.env.link_cont, link_obj,
expected_content_location="%s/%s" % (self.env.tgt_cont,
target_obj))
expected_content_location='%s/%s' % (
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):
link_obj = uuid4().hex

View File

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