Skip to content

Commit d1f1753

Browse files
committed
api:put_object not saving user metadata without x-amz-meta- prefix (#655)
Prefix user-defined metadata with x-amz-meta- if not already prefixed.
1 parent 2aca556 commit d1f1753

File tree

4 files changed

+100
-12
lines changed

4 files changed

+100
-12
lines changed

minio/api.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@
6969
is_valid_bucket_name, PartMetadata, read_full,
7070
is_valid_bucket_notification_config,
7171
get_s3_region_from_endpoint,
72-
mkdir_p, dump_http)
72+
mkdir_p, dump_http, amzprefix_user_metadata,
73+
is_supported_header,is_amz_header)
7374
from .helpers import (MAX_MULTIPART_OBJECT_SIZE,
7475
MAX_POOL_SIZE,
7576
MIN_PART_SIZE)
@@ -732,9 +733,10 @@ def put_object(self, bucket_name, object_name, data, length,
732733
if not metadata:
733734
metadata = {}
734735

736+
metadata = amzprefix_user_metadata(metadata)
737+
735738
metadata['Content-Type'] = 'application/octet-stream' if \
736739
not content_type else content_type
737-
738740
if length > MIN_PART_SIZE:
739741
return self._stream_put_object(bucket_name, object_name,
740742
data, length, metadata=metadata)
@@ -903,18 +905,10 @@ def stat_object(self, bucket_name, object_name):
903905
content_type = response.headers.get('content-type', '')
904906
last_modified = response.headers.get('last-modified')
905907

906-
## Supported headers for object.
907-
supported_headers = [
908-
'cache-control',
909-
'content-encoding',
910-
'content-disposition',
911-
## Add more supported headers here.
912-
]
913-
914908
## Capture only custom metadata.
915909
custom_metadata = dict()
916910
for k in response.headers:
917-
if k in supported_headers or k.lower().startswith('x-amz-meta-'):
911+
if is_supported_header(k) or is_amz_header(k):
918912
custom_metadata[k] = response.headers.get(k)
919913

920914
if last_modified:

minio/helpers.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,3 +603,36 @@ def optimal_part_info(length):
603603
# Last part size.
604604
last_part_size = length - int(total_parts_count-1)*part_size
605605
return total_parts_count, part_size, last_part_size
606+
607+
# return a new metadata dictionary where user defined metadata keys
608+
# are prefixed by "x-amz-meta-"
609+
def amzprefix_user_metadata(metadata):
610+
m = dict()
611+
for k,v in metadata.items():
612+
if is_amz_header(k) or is_supported_header(k) or is_storageclass_header(k):
613+
m[k] = v
614+
else:
615+
m["X-Amz-Meta-" + k] = v
616+
return m
617+
618+
# returns true if amz s3 system defined metadata
619+
def is_amz_header(key):
620+
key = key.lower()
621+
return key.startswith("x-amz-meta") or key == "x-amz-acl" or key.startswith("x-amz-server-side-encryption")
622+
623+
# returns true if a standard supported header
624+
def is_supported_header(key):
625+
## Supported headers for object.
626+
supported_headers = [
627+
"cache-control",
628+
"content-encoding",
629+
"content-disposition",
630+
"content-language",
631+
"x-amz-website-redirect-location",
632+
## Add more supported headers here.
633+
]
634+
return key.lower() in supported_headers
635+
636+
# returns true if header is a storage class header
637+
def is_storageclass_header(key):
638+
return key.lower() == "x-amz-storage-class"

tests/functional/tests.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ def test_put_object(client, log_output):
582582
log_output.args['length'] = MB_11 = 11*1024*1024 # 11MiB.
583583
MB_11_reader = LimitedRandomReader(MB_11)
584584
log_output.args['data'] = 'LimitedRandomReader(MB_11)'
585-
log_output.args['metadata'] = metadata = {'x-amz-meta-testing': 'value'}
585+
log_output.args['metadata'] = metadata = {'x-amz-meta-testing': 'value','test-key':'value2'}
586586
log_output.args['content_type'] = content_type='application/octet-stream'
587587
client.put_object(bucket_name,
588588
object_name+'-metadata',
@@ -599,6 +599,8 @@ def test_put_object(client, log_output):
599599
if value != 'value':
600600
raise ValueError('Metadata key has unexpected'
601601
' value {0}'.format(value))
602+
if 'X-Amz-Meta-Test-Key' not in st_obj.metadata:
603+
raise ValueError("Metadata key 'x-amz-meta-test-key' not found")
602604
except Exception as err:
603605
raise Exception(err)
604606
finally:

tests/unit/header_value_test.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# -*- coding: utf-8 -*-
2+
# Minio Python Library for Amazon S3 Compatible Cloud Storage,
3+
# (C) 2018 Minio, Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
from nose.tools import eq_, raises
18+
from unittest import TestCase
19+
20+
from minio.helpers import is_storageclass_header,is_amz_header,is_supported_header,amzprefix_user_metadata
21+
22+
class HeaderTests(TestCase):
23+
def test_is_supported_header(self):
24+
eq_(is_supported_header("content-type"),False)
25+
eq_(is_supported_header("Content-Type"),False)
26+
eq_(is_supported_header("cOntent-TypE"),False)
27+
eq_(is_supported_header("x-amz-meta-me"),False)
28+
eq_(is_supported_header("Cache-Control"),True)
29+
eq_(is_supported_header("content-encoding"),True)
30+
eq_(is_supported_header("content-disposition"),True)
31+
eq_(is_supported_header("content-language"),True)
32+
eq_(is_supported_header("x-amz-website-redirect-location"),True)
33+
def test_is_amz_header(self):
34+
eq_(is_amz_header("x-amz-meta-status-code"),True)
35+
eq_(is_amz_header("X-Amz-Meta-status-code"),True)
36+
eq_(is_amz_header("X_AMZ_META-VALUE"),False)
37+
eq_(is_amz_header("content-type"),False)
38+
eq_(is_amz_header("x-amz-server-side-encryption"),True)
39+
def test_is_storageclass_header(self):
40+
eq_(is_storageclass_header("x-amz-storage-classs"),False)
41+
eq_(is_storageclass_header("x-amz-storage-class"),True)
42+
def test_amzprefix_user_metadata(self):
43+
metadata = {
44+
'x-amz-meta-testing': 'values',
45+
'x-amz-meta-setting': 'zombies',
46+
'amz-meta-setting': 'zombiesddd',
47+
'hhh':34,
48+
'u_u': 'dd',
49+
'y-fu-bar': 'zoo',
50+
'Content-Type': 'application/csv',
51+
'x-amz-storage-class': 'REDUCED_REDUNDANCY',
52+
'content-language':'fr'
53+
}
54+
m = amzprefix_user_metadata(metadata)
55+
self.assertTrue('X-Amz-Meta-hhh',m)
56+
self.assertTrue('Content-Type',m)
57+
self.assertTrue('x-amz-storage-class',m)
58+
self.assertTrue('content-language',m)
59+
self.assertTrue('X-Amz-Meta-amz-meta-setting',m)

0 commit comments

Comments
 (0)