diff --git a/etc/object-server.conf-sample b/etc/object-server.conf-sample index ea9c22fac3..9d57f8f02d 100644 --- a/etc/object-server.conf-sample +++ b/etc/object-server.conf-sample @@ -30,6 +30,10 @@ use = egg:swift#object # slow = 1 # on PUTs, sync data every n MB # mb_per_sync = 512 +# Comma separated list of headers that can be set in metadata on an object. +# This list is in addition to X-Object-Meta-* headers and cannot include +# Content-Type, etag, Content-Length, or deleted +# allowed_headers = Content-Encoding [object-replicator] # You can override the default log routing for this app here (don't use set!): diff --git a/swift/obj/server.py b/swift/obj/server.py index ee804fa98e..f0fb302687 100644 --- a/swift/obj/server.py +++ b/swift/obj/server.py @@ -52,6 +52,8 @@ PICKLE_PROTOCOL = 2 METADATA_KEY = 'user.swift.metadata' MAX_OBJECT_NAME_LENGTH = 1024 KEEP_CACHE_SIZE = (5 * 1024 * 1024) +# keep these lower-case +DISALLOWED_HEADERS = set('content-length content-type deleted etag'.split()) def read_metadata(fd): @@ -137,8 +139,7 @@ class DiskFile(object): if self.meta_file: with open(self.meta_file) as mfp: for key in self.metadata.keys(): - if key.lower() not in ('content-type', - 'deleted', 'content-length', 'etag'): + if key.lower() not in DISALLOWED_HEADERS: del self.metadata[key] self.metadata.update(read_metadata(mfp)) @@ -278,6 +279,10 @@ class ObjectController(object): self.max_upload_time = int(conf.get('max_upload_time', 86400)) self.slow = int(conf.get('slow', 0)) self.bytes_per_sync = int(conf.get('mb_per_sync', 512)) * 1024 * 1024 + default_allowed_headers = 'content-encoding' + self.allowed_headers = set(i.strip().lower() for i in \ + conf.get('allowed_headers', \ + default_allowed_headers).split(',') if i.strip()) def container_update(self, op, account, container, obj, headers_in, headers_out, objdevice): @@ -353,9 +358,11 @@ class ObjectController(object): metadata = {'X-Timestamp': request.headers['x-timestamp']} metadata.update(val for val in request.headers.iteritems() if val[0].lower().startswith('x-object-meta-')) - if 'content-encoding' in request.headers: - metadata['Content-Encoding'] = \ - request.headers['Content-Encoding'] + for header_key in self.allowed_headers: + if header_key in request.headers: + header_caps = \ + header_key.replace('-', ' ').title().replace(' ', '-') + metadata[header_caps] = request.headers[header_key] with file.mkstemp() as (fd, tmppath): file.put(fd, tmppath, metadata, extension='.meta') return response_class(request=request) @@ -420,9 +427,11 @@ class ObjectController(object): metadata.update(val for val in request.headers.iteritems() if val[0].lower().startswith('x-object-meta-') and len(val[0]) > 14) - if 'content-encoding' in request.headers: - metadata['Content-Encoding'] = \ - request.headers['Content-Encoding'] + for header_key in self.allowed_headers: + if header_key in request.headers: + header_caps = \ + header_key.replace('-', ' ').title().replace(' ', '-') + metadata[header_caps] = request.headers[header_key] file.put(fd, tmppath, metadata) file.unlinkold(metadata['X-Timestamp']) self.container_update('PUT', account, container, obj, request.headers, @@ -487,7 +496,8 @@ class ObjectController(object): request=request, conditional_response=True) for key, value in file.metadata.iteritems(): if key == 'X-Object-Manifest' or \ - key.lower().startswith('x-object-meta-'): + key.lower().startswith('x-object-meta-') or \ + key.lower() in self.allowed_headers: response.headers[key] = value response.etag = file.metadata['ETag'] response.last_modified = float(file.metadata['X-Timestamp']) diff --git a/test/unit/obj/test_server.py b/test/unit/obj/test_server.py index d91ae85cf2..048d1a60bb 100644 --- a/test/unit/obj/test_server.py +++ b/test/unit/obj/test_server.py @@ -57,10 +57,14 @@ class TestObjectController(unittest.TestCase): def test_POST_update_meta(self): """ Test swift.object_server.ObjectController.POST """ + test_headers = 'content-encoding foo bar'.split() + self.object_controller.allowed_headers = set(test_headers) timestamp = normalize_timestamp(time()) req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, headers={'X-Timestamp': timestamp, 'Content-Type': 'application/x-test', + 'Foo': 'fooheader', + 'Baz': 'bazheader', 'X-Object-Meta-1': 'One', 'X-Object-Meta-Two': 'Two'}) req.body = 'VERIFY' @@ -74,6 +78,8 @@ class TestObjectController(unittest.TestCase): 'X-Object-Meta-3': 'Three', 'X-Object-Meta-4': 'Four', 'Content-Encoding': 'gzip', + 'Foo': 'fooheader', + 'Bar': 'barheader', 'Content-Type': 'application/x-test'}) resp = self.object_controller.POST(req) self.assertEquals(resp.status_int, 202) @@ -84,6 +90,9 @@ class TestObjectController(unittest.TestCase): "X-Object-Meta-Two" not in resp.headers and \ "X-Object-Meta-3" in resp.headers and \ "X-Object-Meta-4" in resp.headers and \ + "Foo" in resp.headers and \ + "Bar" in resp.headers and \ + "Baz" not in resp.headers and \ "Content-Encoding" in resp.headers) self.assertEquals(resp.headers['Content-Type'], 'application/x-test') @@ -98,6 +107,8 @@ class TestObjectController(unittest.TestCase): resp = self.object_controller.GET(req) self.assert_("X-Object-Meta-3" not in resp.headers and \ "X-Object-Meta-4" not in resp.headers and \ + "Foo" not in resp.headers and \ + "Bar" not in resp.headers and \ "Content-Encoding" not in resp.headers) self.assertEquals(resp.headers['Content-Type'], 'application/x-test')