def test_symlink_target(self): if 'symlink' not in self.cluster_info: raise unittest.SkipTest("Symlink not enabled in proxy; can't test " "symlink to reserved name") int_client = self.make_internal_client() # create link container first, ensure account gets created too client.put_container(self.url, self.token, 'c1') # Create reserve named container tgt_cont = get_reserved_name('container-%s' % uuid4()) int_client.create_container(self.account, tgt_cont) # sanity, user can't get to reserved name with self.assertRaises(ClientException) as cm: client.head_container(self.url, self.token, tgt_cont) self.assertEqual(412, cm.exception.http_status) tgt_obj = get_reserved_name('obj-%s' % uuid4()) int_client.upload_object(BytesIO(b'target object'), self.account, tgt_cont, tgt_obj) metadata = int_client.get_object_metadata(self.account, tgt_cont, tgt_obj) etag = metadata['etag'] # users can write a dynamic symlink that targets a reserved # name object client.put_object(self.url, self.token, 'c1', 'symlink', headers={ 'X-Symlink-Target': '%s/%s' % (tgt_cont, tgt_obj), 'Content-Type': 'application/symlink', }) # but can't read the symlink with self.assertRaises(ClientException) as cm: client.get_object(self.url, self.token, 'c1', 'symlink') self.assertEqual(412, cm.exception.http_status) # user's can't create static symlink to reserved name with self.assertRaises(ClientException) as cm: client.put_object(self.url, self.token, 'c1', 'static-symlink', headers={ 'X-Symlink-Target': '%s/%s' % (tgt_cont, tgt_obj), 'X-Symlink-Target-Etag': etag, 'Content-Type': 'application/symlink', }) self.assertEqual(412, cm.exception.http_status) # clean-up client.delete_object(self.url, self.token, 'c1', 'symlink') int_client.delete_object(self.account, tgt_cont, tgt_obj) int_client.delete_container(self.account, tgt_cont)
def setUp(self): super(TestReservedNamespaceMergePolicyIndex, self).setUp() self.container_name = get_reserved_name('container', str(uuid.uuid4())) self.object_name = get_reserved_name('object', str(uuid.uuid4())) self.brain = InternalBrainSplitter('/etc/swift/internal-client.conf', self.container_name, self.object_name, 'container')
def test_validate_internal_container(self): self.assertIsNone(rh.validate_internal_container('AUTH_foo', 'bar')) self.assertIsNone( rh.validate_internal_container(rh.get_reserved_name('AUTH_foo'), 'bar')) self.assertIsNone( rh.validate_internal_container('foo', rh.get_reserved_name('bar'))) self.assertIsNone( rh.validate_internal_container(rh.get_reserved_name('AUTH_foo'), rh.get_reserved_name('bar'))) with self.assertRaises(HTTPException) as raised: rh.validate_internal_container('AUTH_foo' + rh.RESERVED, 'bar') e = raised.exception self.assertEqual(e.status_int, 400) self.assertEqual(str(e), '400 Bad Request') self.assertEqual(e.body, b"Invalid reserved-namespace account") with self.assertRaises(HTTPException) as raised: rh.validate_internal_container('AUTH_foo', 'bar' + rh.RESERVED) e = raised.exception self.assertEqual(e.status_int, 400) self.assertEqual(str(e), '400 Bad Request') self.assertEqual(e.body, b"Invalid reserved-namespace container") # These should always be operating on split_path outputs so this # shouldn't really be an issue, but just in case... for acct in ('', None): with self.assertRaises(ValueError) as raised: rh.validate_internal_container(acct, 'bar') self.assertEqual(raised.exception.args[0], 'Account is required')
def test_validate_internal_name(self): self.assertIsNone(rh._validate_internal_name('foo')) self.assertIsNone( rh._validate_internal_name(rh.get_reserved_name('foo'))) self.assertIsNone( rh._validate_internal_name(rh.get_reserved_name('foo', 'bar'))) self.assertIsNone(rh._validate_internal_name('')) self.assertIsNone(rh._validate_internal_name(rh.RESERVED))
def test_account_listing_reserved_names(self): broker = backend.AccountBroker(':memory:', account='a') put_timestamp = next(self.ts) now = time.time() with mock.patch('time.time', new=lambda: now): broker.initialize(put_timestamp.internal) container_timestamp = next(self.ts) broker.put_container(get_reserved_name('foo'), container_timestamp.internal, 0, 10, 100, 0) req = Request.blank('') resp = utils.account_listing_response( 'a', req, 'application/json', broker) self.assertEqual(resp.status_int, 200) expected = HeaderKeyDict({ 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': 2, 'X-Account-Container-Count': 1, 'X-Account-Object-Count': 10, 'X-Account-Bytes-Used': 100, 'X-Timestamp': Timestamp(now).normal, 'X-PUT-Timestamp': put_timestamp.normal, 'X-Account-Storage-Policy-Zero-Container-Count': 1, 'X-Account-Storage-Policy-Zero-Object-Count': 10, 'X-Account-Storage-Policy-Zero-Bytes-Used': 100, }) self.assertEqual(expected, resp.headers) self.assertEqual(b'[]', resp.body) req = Request.blank('', headers={ 'X-Backend-Allow-Reserved-Names': 'true'}) resp = utils.account_listing_response( 'a', req, 'application/json', broker) self.assertEqual(resp.status_int, 200) expected = HeaderKeyDict({ 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': 97, 'X-Account-Container-Count': 1, 'X-Account-Object-Count': 10, 'X-Account-Bytes-Used': 100, 'X-Timestamp': Timestamp(now).normal, 'X-PUT-Timestamp': put_timestamp.normal, 'X-Account-Storage-Policy-Zero-Container-Count': 1, 'X-Account-Storage-Policy-Zero-Object-Count': 10, 'X-Account-Storage-Policy-Zero-Bytes-Used': 100, }) self.assertEqual(expected, resp.headers) expected = [{ "last_modified": container_timestamp.isoformat, "count": 10, "bytes": 100, "name": get_reserved_name('foo'), }] self.assertEqual(sorted(json.dumps(expected).encode('ascii')), sorted(resp.body))
def put_data(self): int_client = self.make_internal_client() int_client.create_account(self.account) container = get_reserved_name('container', str(uuid4())) int_client.create_container(self.account, container, headers={'X-Storage-Policy': self.policy.name}) obj = get_reserved_name('object', str(uuid4())) int_client.upload_object( BytesIO(b'VERIFY'), self.account, container, obj)
def test_simple_crud(self): int_client = self.make_internal_client() # Create reserve named container user_cont = 'container-%s' % uuid4() reserved_cont = get_reserved_name('container-%s' % uuid4()) client.put_container(self.url, self.token, user_cont) int_client.create_container(self.account, reserved_cont) # Check that we can list both reserved and non-reserved containers self.assertEqual( [reserved_cont, user_cont], [c['name'] for c in int_client.iter_containers(self.account)]) # sanity, user can't get to reserved name with self.assertRaises(ClientException) as cm: client.head_container(self.url, self.token, reserved_cont) self.assertEqual(412, cm.exception.http_status) user_obj = 'obj-%s' % uuid4() reserved_obj = get_reserved_name('obj-%s' % uuid4()) # InternalClient can write & read reserved names fine int_client.upload_object(BytesIO(b'data'), self.account, reserved_cont, reserved_obj) int_client.get_object_metadata(self.account, reserved_cont, reserved_obj) _, _, app_iter = int_client.get_object(self.account, reserved_cont, reserved_obj) self.assertEqual(b''.join(app_iter), b'data') self.assertEqual([reserved_obj], [ o['name'] for o in int_client.iter_objects(self.account, reserved_cont) ]) # But reserved objects must be in reserved containers, and # user objects must be in user containers (at least for now) int_client.upload_object(BytesIO(b'data'), self.account, reserved_cont, user_obj, acceptable_statuses=(400, )) int_client.upload_object(BytesIO(b'data'), self.account, user_cont, reserved_obj, acceptable_statuses=(400, )) # Make sure we can clean up, too int_client.delete_object(self.account, reserved_cont, reserved_obj) int_client.delete_container(self.account, reserved_cont)
def get_metadata_resp_headers(self, meta): headers = {} system = meta.get('system') or {} # sys.m2.ctime is microseconds ctime = float(system.get('sys.m2.ctime', 0)) / 1000000.0 headers.update({ 'X-Container-Object-Count': system.get('sys.m2.objects', 0), 'X-Container-Bytes-Used': system.get('sys.m2.usage', 0), 'X-Timestamp': Timestamp(ctime).normal, # FIXME: save modification time somewhere 'X-PUT-Timestamp': Timestamp(ctime).normal, }) for (k, v) in meta['properties'].items(): if v and (k.lower() in self.pass_through_headers or is_sys_or_user_meta('container', k)): headers[k] = v # HACK: oio-sds always sets version numbers, so let some middlewares # think that versioning is always enabled. if SYSMETA_VERSIONS_CONT not in headers and 'sys.user.name' in system: try: v_con = get_reserved_name('versions', system['sys.user.name']) headers[SYSMETA_VERSIONS_CONT] = v_con except ValueError: # sys.user.name contains reserved characters # -> this is probably a versioning container. pass return headers
def setUp(self): super(TestAccountReaper, self).setUp() self.all_objects = [] int_client = self.make_internal_client() # upload some containers body = b'test-body' for policy in ENABLED_POLICIES: container = 'container-%s-%s' % (policy.name, uuid.uuid4()) client.put_container(self.url, self.token, container, headers={'X-Storage-Policy': policy.name}) obj = 'object-%s' % uuid.uuid4() client.put_object(self.url, self.token, container, obj, body) self.all_objects.append((policy, container, obj)) # Also create some reserved names container = get_reserved_name('reserved', policy.name, str(uuid.uuid4())) int_client.create_container( self.account, container, headers={'X-Storage-Policy': policy.name}) obj = get_reserved_name('object', str(uuid.uuid4())) int_client.upload_object(BytesIO(body), self.account, container, obj) self.all_objects.append((policy, container, obj)) policy.load_ring('/etc/swift') Manager(['container-updater']).once() headers = client.head_account(self.url, self.token) self.assertEqual(int(headers['x-account-container-count']), len(self.all_objects)) self.assertEqual(int(headers['x-account-object-count']), len(self.all_objects)) self.assertEqual(int(headers['x-account-bytes-used']), len(self.all_objects) * len(body)) part, nodes = self.account_ring.get_nodes(self.account) for node in nodes: direct_delete_account(node, part, self.account)
def test_validate_internal_account(self): self.assertIsNone(rh.validate_internal_account('AUTH_foo')) self.assertIsNone( rh.validate_internal_account(rh.get_reserved_name('AUTH_foo'))) with self.assertRaises(HTTPException) as raised: rh.validate_internal_account('AUTH_foo' + rh.RESERVED) e = raised.exception self.assertEqual(e.status_int, 400) self.assertEqual(str(e), '400 Bad Request') self.assertEqual(e.body, b"Invalid reserved-namespace account")
def test_get_reserved_name(self): expectations = { tuple(): rh.RESERVED, ('', ): rh.RESERVED, ('foo', ): rh.RESERVED + 'foo', ('foo', 'bar'): rh.RESERVED + 'foo' + rh.RESERVED + 'bar', ('foo', ''): rh.RESERVED + 'foo' + rh.RESERVED, ('', ''): rh.RESERVED * 2, } failures = [] for parts, expected in expectations.items(): name = rh.get_reserved_name(*parts) if name != expected: failures.append('get given %r expected %r != %r' % (parts, expected, name)) if failures: self.fail('Unexpected reults:\n' + '\n'.join(failures))
def test_invalid_get_reserved_name(self): self.assertRaises(ValueError) with self.assertRaises(ValueError) as ctx: rh.get_reserved_name('foo', rh.RESERVED + 'bar', 'baz') self.assertEqual(str(ctx.exception), 'Invalid reserved part in components')
def handle_versioned_request(self, req, versions_cont, api_version, account, container, obj, is_enabled, version): """ Handle 'version-id' request for object resource. When a request contains a ``version-id=<id>`` parameter, the request is acted upon the actual version of that object. Version-aware operations require that the container is versioned, but do not require that the versioning is currently enabled. Users should be able to operate on older versions of an object even if versioning is currently suspended. PUT and POST requests are not allowed as that would overwrite the contents of the versioned object. :param req: The original request :param versions_cont: container holding versions of the requested obj :param api_version: should be v1 unless swift bumps api version :param account: account name string :param container: container name string :param object: object name string :param is_enabled: is versioning currently enabled :param version: version of the object to act on """ if not versions_cont and version != 'null': raise HTTPBadRequest( 'version-aware operations require that the container is ' 'versioned', request=req) req.environ['oio.query'] = {'version': version} if version != 'null': try: int(version) except ValueError: raise HTTPBadRequest('Invalid version parameter', request=req) if req.method == 'DELETE': return self.handle_delete_version(req, versions_cont, api_version, account, container, obj, is_enabled, version) elif req.method == 'PUT': return self.handle_put_version(req, versions_cont, api_version, account, container, obj, is_enabled, version) if version == 'null': resp = req.get_response(self.app) if resp.is_success: if get_reserved_name('versions', '') in wsgi_unquote( resp.headers.get('Content-Location', '')): # Have a latest version, but it's got a real version-id. # Since the user specifically asked for null, return 404 close_if_possible(resp.app_iter) raise HTTPNotFound(request=req) resp.headers['X-Object-Version-Id'] = 'null' if req.method == 'HEAD': drain_and_close(resp) return resp else: resp = req.get_response(self.app) if resp.is_success: resp.headers['X-Object-Version-Id'] = version # Well, except for some delete marker business... is_del_marker = DELETE_MARKER_CONTENT_TYPE == resp.headers.get( 'X-Backend-Content-Type', resp.headers['Content-Type']) if req.method == 'HEAD': drain_and_close(resp) if is_del_marker: hdrs = { 'X-Object-Version-Id': version, 'Content-Type': DELETE_MARKER_CONTENT_TYPE } raise HTTPNotFound(request=req, headers=hdrs) return resp
def test_missing_versions_container(self): versions_header_key = 'X-Versions-Enabled' # Create container1 container_name = 'container1' obj_name = 'object1' client.put_container(self.url, self.token, container_name) # Write some data client.put_object(self.url, self.token, container_name, obj_name, b'null version') # Enable versioning hdrs = {versions_header_key: 'True'} client.post_container(self.url, self.token, container_name, hdrs) # But directly delete hidden container to leave an orphan primary # container self.direct_delete_container( container=get_reserved_name('versions', container_name)) # Could be worse; we can still list versions and GET data _headers, all_versions = client.get_container(self.url, self.token, container_name, query_string='versions') self.assertEqual(len(all_versions), 1) self.assertEqual(all_versions[0]['name'], obj_name) self.assertEqual(all_versions[0]['version_id'], 'null') _headers, data = client.get_object(self.url, self.token, container_name, obj_name) self.assertEqual(data, b'null version') _headers, data = client.get_object(self.url, self.token, container_name, obj_name, query_string='version-id=null') self.assertEqual(data, b'null version') # But most any write is going to fail with self.assertRaises(client.ClientException) as caught: client.put_object(self.url, self.token, container_name, obj_name, b'new version') self.assertEqual(caught.exception.http_status, 500) with self.assertRaises(client.ClientException) as caught: client.delete_object(self.url, self.token, container_name, obj_name) self.assertEqual(caught.exception.http_status, 500) # Version-aware delete can work, though! client.delete_object(self.url, self.token, container_name, obj_name, query_string='version-id=null') # Re-enabling versioning should square us hdrs = {versions_header_key: 'True'} client.post_container(self.url, self.token, container_name, hdrs) client.put_object(self.url, self.token, container_name, obj_name, b'new version') _headers, all_versions = client.get_container(self.url, self.token, container_name, query_string='versions') self.assertEqual(len(all_versions), 1) self.assertEqual(all_versions[0]['name'], obj_name) self.assertNotEqual(all_versions[0]['version_id'], 'null') _headers, data = client.get_object(self.url, self.token, container_name, obj_name) self.assertEqual(data, b'new version')
def get_object_name(self, name): return get_reserved_name(name)
def test_valid_account_with_reserved(self): body_len = len(self.fake_account_listing_with_reserved) self.fake_swift.register('GET', '/v1/a\xe2\x98\x83', HTTPOk, { 'Content-Length': str(body_len), 'Content-Type': 'application/json', }, self.fake_account_listing_with_reserved) req = Request.blank('/v1/a\xe2\x98\x83') resp = req.get_response(self.app) self.assertEqual(resp.body, b'bar\nfoo_\n') self.assertEqual(resp.headers['Content-Type'], 'text/plain; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', '/v1/a\xe2\x98\x83?format=json')) self.assertEqual(self.logger.get_lines_for_level('warning'), [ "Account listing for a%E2%98%83 had reserved byte in name: " "'\\x00bar\\x00versions'", "Account listing for a%E2%98%83 had reserved byte in subdir: " "'\\x00foo_'", ]) req = Request.blank('/v1/a\xe2\x98\x83', headers={'X-Backend-Allow-Reserved-Names': 'true'}) resp = req.get_response(self.app) self.assertEqual( resp.body, b'bar\n%s\nfoo_\n%s\n' % ( get_reserved_name('bar', 'versions').encode('ascii'), get_reserved_name('foo_').encode('ascii'), )) self.assertEqual(resp.headers['Content-Type'], 'text/plain; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', '/v1/a\xe2\x98\x83?format=json')) req = Request.blank('/v1/a\xe2\x98\x83?format=txt') resp = req.get_response(self.app) self.assertEqual(resp.body, b'bar\nfoo_\n') self.assertEqual(resp.headers['Content-Type'], 'text/plain; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', '/v1/a\xe2\x98\x83?format=json')) req = Request.blank('/v1/a\xe2\x98\x83?format=txt', headers={'X-Backend-Allow-Reserved-Names': 'true'}) resp = req.get_response(self.app) self.assertEqual( resp.body, b'bar\n%s\nfoo_\n%s\n' % ( get_reserved_name('bar', 'versions').encode('ascii'), get_reserved_name('foo_').encode('ascii'), )) self.assertEqual(resp.headers['Content-Type'], 'text/plain; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', '/v1/a\xe2\x98\x83?format=json')) req = Request.blank('/v1/a\xe2\x98\x83?format=json') resp = req.get_response(self.app) self.assertEqual(json.loads(resp.body), json.loads(self.fake_account_listing)) self.assertEqual(resp.headers['Content-Type'], 'application/json; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', '/v1/a\xe2\x98\x83?format=json')) req = Request.blank('/v1/a\xe2\x98\x83?format=json', headers={'X-Backend-Allow-Reserved-Names': 'true'}) resp = req.get_response(self.app) self.assertEqual(json.loads(resp.body), json.loads(self.fake_account_listing_with_reserved)) self.assertEqual(resp.headers['Content-Type'], 'application/json; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', '/v1/a\xe2\x98\x83?format=json')) req = Request.blank('/v1/a\xe2\x98\x83?format=xml') resp = req.get_response(self.app) self.assertEqual(resp.body.split(b'\n'), [ b'<?xml version="1.0" encoding="UTF-8"?>', b'<account name="a\xe2\x98\x83">', b'<container><name>bar</name><count>0</count><bytes>0</bytes>' b'<last_modified>1970-01-01T00:00:00.000000</last_modified>' b'</container>', b'<subdir name="foo_" />', b'</account>', ]) self.assertEqual(resp.headers['Content-Type'], 'application/xml; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', '/v1/a\xe2\x98\x83?format=json')) req = Request.blank('/v1/a\xe2\x98\x83?format=xml', headers={'X-Backend-Allow-Reserved-Names': 'true'}) resp = req.get_response(self.app) self.assertEqual(resp.body.split(b'\n'), [ b'<?xml version="1.0" encoding="UTF-8"?>', b'<account name="a\xe2\x98\x83">', b'<container><name>bar</name><count>0</count><bytes>0</bytes>' b'<last_modified>1970-01-01T00:00:00.000000</last_modified>' b'</container>', b'<container><name>%s</name>' b'<count>0</count><bytes>0</bytes>' b'<last_modified>1970-01-01T00:00:00.000000</last_modified>' b'</container>' % get_reserved_name('bar', 'versions').encode('ascii'), b'<subdir name="foo_" />', b'<subdir name="%s" />' % get_reserved_name('foo_').encode('ascii'), b'</account>', ]) self.assertEqual(resp.headers['Content-Type'], 'application/xml; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', '/v1/a\xe2\x98\x83?format=json'))
def setUp(self): self.fake_swift = FakeSwift() self.logger = debug_logger('test-listing') self.app = listing_formats.ListingFilter(self.fake_swift, {}, logger=self.logger) self.fake_account_listing = json.dumps([ { 'name': 'bar', 'bytes': 0, 'count': 0, 'last_modified': '1970-01-01T00:00:00.000000' }, { 'subdir': 'foo_' }, ]).encode('ascii') self.fake_container_listing = json.dumps([ { 'name': 'bar', 'hash': 'etag', 'bytes': 0, 'content_type': 'text/plain', 'last_modified': '1970-01-01T00:00:00.000000' }, { 'subdir': 'foo/' }, ]).encode('ascii') self.fake_account_listing_with_reserved = json.dumps([ { 'name': 'bar', 'bytes': 0, 'count': 0, 'last_modified': '1970-01-01T00:00:00.000000' }, { 'name': get_reserved_name('bar', 'versions'), 'bytes': 0, 'count': 0, 'last_modified': '1970-01-01T00:00:00.000000' }, { 'subdir': 'foo_' }, { 'subdir': get_reserved_name('foo_') }, ]).encode('ascii') self.fake_container_listing_with_reserved = json.dumps([ { 'name': 'bar', 'hash': 'etag', 'bytes': 0, 'content_type': 'text/plain', 'last_modified': '1970-01-01T00:00:00.000000' }, { 'name': get_reserved_name('bar', 'extra data'), 'hash': 'etag', 'bytes': 0, 'content_type': 'text/plain', 'last_modified': '1970-01-01T00:00:00.000000' }, { 'subdir': 'foo/' }, { 'subdir': get_reserved_name('foo/') }, ]).encode('ascii')
def test_valid_container_with_reserved(self): path = '/v1/a\xe2\x98\x83/c\xf0\x9f\x8c\xb4' body_len = len(self.fake_container_listing_with_reserved) self.fake_swift.register('GET', path, HTTPOk, { 'Content-Length': str(body_len), 'Content-Type': 'application/json', }, self.fake_container_listing_with_reserved) req = Request.blank(path) resp = req.get_response(self.app) self.assertEqual(resp.body, b'bar\nfoo/\n') self.assertEqual(resp.headers['Content-Type'], 'text/plain; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', path + '?format=json')) self.assertEqual(self.logger.get_lines_for_level('warning'), [ "Container listing for a%E2%98%83/c%F0%9F%8C%B4 had reserved byte " "in name: '\\x00bar\\x00extra data'", "Container listing for a%E2%98%83/c%F0%9F%8C%B4 had reserved byte " "in subdir: '\\x00foo/'", ]) req = Request.blank(path, headers={'X-Backend-Allow-Reserved-Names': 'true'}) resp = req.get_response(self.app) self.assertEqual( resp.body, b'bar\n%s\nfoo/\n%s\n' % ( get_reserved_name('bar', 'extra data').encode('ascii'), get_reserved_name('foo/').encode('ascii'), )) self.assertEqual(resp.headers['Content-Type'], 'text/plain; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', path + '?format=json')) req = Request.blank(path + '?format=txt') resp = req.get_response(self.app) self.assertEqual(resp.body, b'bar\nfoo/\n') self.assertEqual(resp.headers['Content-Type'], 'text/plain; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', path + '?format=json')) req = Request.blank(path + '?format=txt', headers={'X-Backend-Allow-Reserved-Names': 'true'}) resp = req.get_response(self.app) self.assertEqual( resp.body, b'bar\n%s\nfoo/\n%s\n' % ( get_reserved_name('bar', 'extra data').encode('ascii'), get_reserved_name('foo/').encode('ascii'), )) self.assertEqual(resp.headers['Content-Type'], 'text/plain; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', path + '?format=json')) req = Request.blank(path + '?format=json') resp = req.get_response(self.app) self.assertEqual(json.loads(resp.body), json.loads(self.fake_container_listing)) self.assertEqual(resp.headers['Content-Type'], 'application/json; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', path + '?format=json')) req = Request.blank(path + '?format=json', headers={'X-Backend-Allow-Reserved-Names': 'true'}) resp = req.get_response(self.app) self.assertEqual(json.loads(resp.body), json.loads(self.fake_container_listing_with_reserved)) self.assertEqual(resp.headers['Content-Type'], 'application/json; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', path + '?format=json')) req = Request.blank(path + '?format=xml') resp = req.get_response(self.app) self.assertEqual( resp.body, b'<?xml version="1.0" encoding="UTF-8"?>\n' b'<container name="c\xf0\x9f\x8c\xb4">' b'<object><name>bar</name><hash>etag</hash><bytes>0</bytes>' b'<content_type>text/plain</content_type>' b'<last_modified>1970-01-01T00:00:00.000000</last_modified>' b'</object>' b'<subdir name="foo/"><name>foo/</name></subdir>' b'</container>') self.assertEqual(resp.headers['Content-Type'], 'application/xml; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', path + '?format=json')) req = Request.blank(path + '?format=xml', headers={'X-Backend-Allow-Reserved-Names': 'true'}) resp = req.get_response(self.app) self.assertEqual( resp.body, b'<?xml version="1.0" encoding="UTF-8"?>\n' b'<container name="c\xf0\x9f\x8c\xb4">' b'<object><name>bar</name><hash>etag</hash><bytes>0</bytes>' b'<content_type>text/plain</content_type>' b'<last_modified>1970-01-01T00:00:00.000000</last_modified>' b'</object>' b'<object><name>%s</name>' b'<hash>etag</hash><bytes>0</bytes>' b'<content_type>text/plain</content_type>' b'<last_modified>1970-01-01T00:00:00.000000</last_modified>' b'</object>' b'<subdir name="foo/"><name>foo/</name></subdir>' b'<subdir name="%s"><name>%s</name></subdir>' b'</container>' % ( get_reserved_name('bar', 'extra data').encode('ascii'), get_reserved_name('foo/').encode('ascii'), get_reserved_name('foo/').encode('ascii'), )) self.assertEqual(resp.headers['Content-Type'], 'application/xml; charset=utf-8') self.assertEqual(self.fake_swift.calls[-1], ('GET', path + '?format=json'))