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:
parent
65660faf69
commit
e5e22ebeba
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user