diff --git a/moto/s3/models.py b/moto/s3/models.py index 69ef827f..15d5fbe6 100644 --- a/moto/s3/models.py +++ b/moto/s3/models.py @@ -10,6 +10,13 @@ class FakeKey(object): self.name = name self.value = value self.last_modified = datetime.datetime.now() + self._metadata = {} + + def set_metadata(self, key, metadata): + self._metadata[key] = metadata + + def get_metadata(self, key): + return self._metadata[key] def append_to_value(self, value): self.value += value @@ -31,6 +38,10 @@ class FakeKey(object): # https://github.com/boto/boto/issues/466 RFC1123 = '%a, %d %b %Y %H:%M:%S GMT' return self.last_modified.strftime(RFC1123) + + @property + def metadata(self): + return self._metadata @property def response_dict(self): diff --git a/moto/s3/responses.py b/moto/s3/responses.py index 766c06e9..2062ccee 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -1,4 +1,5 @@ from urlparse import parse_qs, urlparse +import re from jinja2 import Template @@ -67,6 +68,32 @@ def _bucket_response(request, full_url, headers): # Tried to delete a bucket that still has keys template = Template(S3_DELETE_BUCKET_WITH_ITEMS_ERROR) return 409, headers, template.render(bucket=removed_bucket) + elif method == 'POST': + #POST to bucket-url should create file from form + if hasattr(request, 'form'): + #Not HTTPretty + form = request.form + else: + #HTTPretty, build new form object + form = {} + for kv in request.body.split('&'): + k, v = kv.split('=') + form[k] = v + + key = form['key'] + f = form['file'] + + new_key = s3_backend.set_key(bucket_name, key, f) + + #Metadata + meta_regex = re.compile('^x-amz-meta-([a-zA-Z0-9\-_]+)$', flags=re.IGNORECASE) + for form_id in form: + result = meta_regex.match(form_id) + if result: + meta_key = result.group(0).lower() + metadata = form[form_id] + new_key.set_metadata(meta_key, metadata) + return 200, headers, "" else: raise NotImplementedError("Method {} has not been impelemented in the S3 backend yet".format(method)) @@ -96,7 +123,8 @@ def _key_response(request, full_url, headers): if method == 'GET': key = s3_backend.get_key(bucket_name, key_name) if key: - return key.value + headers.update(key.metadata) + return 200, headers, key.value else: return 404, headers, "" if method == 'PUT': @@ -118,14 +146,25 @@ def _key_response(request, full_url, headers): # Initial data new_key = s3_backend.set_key(bucket_name, key_name, body) request.streaming = True + + #Metadata + meta_regex = re.compile('^x-amz-meta-([a-zA-Z0-9\-_]+)$', flags=re.IGNORECASE) + for header in request.headers: + if isinstance(header, basestring): + result = meta_regex.match(header) + if result: + meta_key = result.group(0).lower() + metadata = request.headers[header] + new_key.set_metadata(meta_key, metadata) template = Template(S3_OBJECT_RESPONSE) headers.update(new_key.response_dict) return 200, headers, template.render(key=new_key) elif method == 'HEAD': key = s3_backend.get_key(bucket_name, key_name) if key: + headers.update(key.metadata) headers.update(key.response_dict) - return 200, headers, S3_OBJECT_RESPONSE + return 200, headers, "" else: return 404, headers, "" elif method == 'DELETE': diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index 54da1b9a..1107c418 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -101,7 +101,17 @@ def test_copy_key(): bucket.get_key("the-key").get_contents_as_string().should.equal("some value") bucket.get_key("new-key").get_contents_as_string().should.equal("some value") + +@mock_s3 +def test_set_metadata(): + conn = boto.connect_s3('the_key', 'the_secret') + bucket = conn.create_bucket("foobar") + key = Key(bucket) + key.key = 'the-key' + key.set_metadata('md', 'Metadatastring') + key.set_contents_from_string("Testval") + bucket.get_key('the-key').get_metadata('md').should.equal('Metadatastring') @freeze_time("2012-01-01 12:00:00") @mock_s3 @@ -163,9 +173,34 @@ def test_get_all_buckets(): buckets.should.have.length_of(2) +@mock_s3 +def test_post_to_bucket(): + conn = boto.connect_s3('the_key', 'the_secret') + bucket = conn.create_bucket("foobar") + + requests.post("https://foobar.s3.amazonaws.com/", { + 'key': 'the-key', + 'file': 'nothing' + }) + + bucket.get_key('the-key').get_contents_as_string().should.equal('nothing') + +@mock_s3 +def test_post_with_metadata_to_bucket(): + conn = boto.connect_s3('the_key', 'the_secret') + bucket = conn.create_bucket("foobar") + + requests.post("https://foobar.s3.amazonaws.com/", { + 'key': 'the-key', + 'file': 'nothing', + 'x-amz-meta-test': 'metadata' + }) + + bucket.get_key('the-key').get_metadata('test').should.equal('metadata') + @mock_s3 def test_bucket_method_not_implemented(): - requests.post.when.called_with("https://foobar.s3.amazonaws.com/").should.throw(NotImplementedError) + requests.patch.when.called_with("https://foobar.s3.amazonaws.com/").should.throw(NotImplementedError) @mock_s3