From 5dc8e59fab9bd2009437914cb574b1c479239d8b Mon Sep 17 00:00:00 2001 From: mfranke <2mf@users.noreply.github.com> Date: Sun, 4 Dec 2016 00:15:24 +0100 Subject: [PATCH] Fix s3bucket_path (#784) * check HTTP header for IPv4 or IPv6 addresses and default to path based S3 * improved IPv4 and IPv6 checking with optional ports * typo * subdomain bucket creation with trailing '/' did not work * Use regex for Host field check to determine IPv4/IPv6 * add testcases for trailing slash, IPv4 and IPv6 --- moto/s3/responses.py | 17 +++---- moto/s3/urls.py | 14 +++++- .../test_bucket_path_server.py | 44 +++++++++++++++++++ 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/moto/s3/responses.py b/moto/s3/responses.py index 5ae776e68..ac1533eb0 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -5,7 +5,6 @@ import re import six from six.moves.urllib.parse import parse_qs, urlparse -import socket import xmltodict from moto.core.responses import _TemplateEnvironmentMixin @@ -57,21 +56,17 @@ class ResponseObject(_TemplateEnvironmentMixin): match = re.match(r'^([^\[\]:]+)(:\d+)?$', host) if match: - try: - socket.inet_pton(socket.AF_INET, match.groups()[0]) - # For IPv4, default to path-based buckets + match = re.match(r'((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}', + match.groups()[0]) + if match: return False - except socket.error: - pass match = re.match(r'^\[(.+)\](:\d+)?$', host) if match: - try: - socket.inet_pton(socket.AF_INET6, match.groups()[0]) - # For IPv6, default to path-based buckets + match = re.match(r'^(((?=.*(::))(?!.*\3.+\3))\3?|[\dA-F]{1,4}:)([\dA-F]{1,4}(\3|:\b)|\2){5}(([\dA-F]{1,4}(\3|:\b|$)|\2){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})\Z', + match.groups()[0], re.IGNORECASE) + if match: return False - except socket.error: - pass path_based = (host == 's3.amazonaws.com' or re.match(r"s3[\.\-]([^.]*)\.amazonaws\.com", host)) return not path_based diff --git a/moto/s3/urls.py b/moto/s3/urls.py index 1f0677956..98686e495 100644 --- a/moto/s3/urls.py +++ b/moto/s3/urls.py @@ -7,13 +7,23 @@ url_bases = [ "https?://(?P[a-zA-Z0-9\-_.]*)\.?s3(.*).amazonaws.com" ] + +def ambiguous_response1(*args, **kwargs): + return S3ResponseInstance.ambiguous_response(*args, **kwargs) + + +def ambiguous_response2(*args, **kwargs): + return S3ResponseInstance.ambiguous_response(*args, **kwargs) + + url_paths = { # subdomain bucket '{0}/$': S3ResponseInstance.bucket_response, # subdomain key of path-based bucket - '{0}/(?P[^/]+)/?$': S3ResponseInstance.ambiguous_response, - + '{0}/(?P[^/]+)$': ambiguous_response1, + # subdomain key of path-based bucket + '{0}/(?P[^/]+)/$': ambiguous_response2, # path-based bucket + key '{0}/(?P[^/]+)/(?P.+)': S3ResponseInstance.key_response, } diff --git a/tests/test_s3bucket_path/test_bucket_path_server.py b/tests/test_s3bucket_path/test_bucket_path_server.py index 4434c02fd..adc5de532 100644 --- a/tests/test_s3bucket_path/test_bucket_path_server.py +++ b/tests/test_s3bucket_path/test_bucket_path_server.py @@ -31,6 +31,16 @@ def test_s3_server_bucket_create(): res.status_code.should.equal(200) res.data.should.contain(b"ListBucketResult") + res = test_client.put('/foobar2/', 'http://localhost:5000') + res.status_code.should.equal(200) + + res = test_client.get('/') + res.data.should.contain(b'foobar2') + + res = test_client.get('/foobar2/', 'http://localhost:5000') + res.status_code.should.equal(200) + res.data.should.contain(b"ListBucketResult") + res = test_client.get('/missing-bucket', 'http://localhost:5000') res.status_code.should.equal(404) @@ -57,3 +67,37 @@ def test_s3_server_post_to_bucket(): res = test_client.get('/foobar2/the-key', 'http://localhost:5000/') res.status_code.should.equal(200) res.data.should.equal(b"nothing") + + +def test_s3_server_put_ipv6(): + backend = server.create_backend_app("s3bucket_path") + test_client = backend.test_client() + + res = test_client.put('/foobar2', 'http://[::]:5000/') + res.status_code.should.equal(200) + + test_client.post('/foobar2', "https://[::]:5000/", data={ + 'key': 'the-key', + 'file': 'nothing' + }) + + res = test_client.get('/foobar2/the-key', 'http://[::]:5000/') + res.status_code.should.equal(200) + res.data.should.equal(b"nothing") + + +def test_s3_server_put_ipv4(): + backend = server.create_backend_app("s3bucket_path") + test_client = backend.test_client() + + res = test_client.put('/foobar2', 'http://127.0.0.1:5000/') + res.status_code.should.equal(200) + + test_client.post('/foobar2', "https://127.0.0.1:5000/", data={ + 'key': 'the-key', + 'file': 'nothing' + }) + + res = test_client.get('/foobar2/the-key', 'http://127.0.0.1:5000/') + res.status_code.should.equal(200) + res.data.should.equal(b"nothing")