diff --git a/swift/common/middleware/formpost.py b/swift/common/middleware/formpost.py index 7eddba0dd9..b6c0a2961a 100644 --- a/swift/common/middleware/formpost.py +++ b/swift/common/middleware/formpost.py @@ -49,11 +49,12 @@ different users' uploads, such as:: Note the form method must be POST and the enctype must be set as "multipart/form-data". -The redirect attribute is the URL to redirect the browser to after -the upload completes. The URL will have status and message query -parameters added to it, indicating the HTTP status code for the -upload (2xx is success) and a possible message for further -information if there was an error (such as "max_file_size exceeded"). +The redirect attribute is the URL to redirect the browser to after the upload +completes. This is an optional parameter. If you are uploading the form via an +XMLHttpRequest the redirect should not be included. The URL will have status +and message query parameters added to it, indicating the HTTP status code for +the upload (2xx is success) and a possible message for further information if +there was an error (such as "max_file_size exceeded"). The max_file_size attribute must be included and indicates the largest single file upload that can be done, in bytes. @@ -73,7 +74,7 @@ sample code for computing the signature:: from hashlib import sha1 from time import time path = '/v1/account/container/object_prefix' - redirect = 'https://myserver.com/some-page' + redirect = 'https://srv.com/some-page' # set to '' if redirect not in form max_file_size = 104857600 max_file_count = 10 expires = int(time() + 600) @@ -350,6 +351,7 @@ class FormPost(object): keys = self._get_keys(env) status = message = '' attributes = {} + subheaders = [] file_count = 0 for fp in _iter_requests(env['wsgi.input'], boundary): hdrs = rfc822.Message(fp, 0) @@ -368,8 +370,8 @@ class FormPost(object): if 'content-type' not in attributes and 'content-type' in hdrs: attributes['content-type'] = \ hdrs['Content-Type'] or 'application/octet-stream' - status, message = self._perform_subrequest(env, attributes, fp, - keys) + status, subheaders, message = \ + self._perform_subrequest(env, attributes, fp, keys) if status[:1] != '2': break else: @@ -388,13 +390,17 @@ class FormPost(object): if not status: status = '400 Bad Request' message = 'no files to process' + + headers = [(k, v) for k, v in subheaders + if k.lower().startswith('access-control')] + redirect = attributes.get('redirect') if not redirect: body = status if message: body = status + '\r\nFormPost: ' + message.title() - headers = [('Content-Type', 'text/plain'), - ('Content-Length', len(body))] + headers.extend([('Content-Type', 'text/plain'), + ('Content-Length', len(body))]) return status, headers, body status = status.split(' ', 1)[0] if '?' in redirect: @@ -404,7 +410,8 @@ class FormPost(object): redirect += 'status=%s&message=%s' % (quote(status), quote(message)) body = '

' \ 'Click to continue...

' % redirect - headers = [('Location', redirect), ('Content-Length', str(len(body)))] + headers.extend( + [('Location', redirect), ('Content-Length', str(len(body)))]) return '303 See Other', headers, body def _perform_subrequest(self, orig_env, attributes, fp, keys): @@ -416,7 +423,7 @@ class FormPost(object): :param attributes: dict of the attributes of the form so far. :param fp: The file-like object containing the request body. :param keys: The account keys to validate the signature with. - :returns: (status_line, message) + :returns: (status_line, headers_list, message) """ if not keys: raise FormUnauthorized('invalid signature') @@ -461,16 +468,18 @@ class FormPost(object): raise FormUnauthorized('invalid signature') substatus = [None] + subheaders = [None] def _start_response(status, headers, exc_info=None): substatus[0] = status + subheaders[0] = headers i = iter(self.app(subenv, _start_response)) try: i.next() except StopIteration: pass - return substatus[0], '' + return substatus[0], subheaders[0], '' def _get_keys(self, env): """ diff --git a/swift/common/wsgi.py b/swift/common/wsgi.py index 1118abbdd5..d5e07d2b49 100644 --- a/swift/common/wsgi.py +++ b/swift/common/wsgi.py @@ -485,7 +485,7 @@ def make_pre_authed_env(env, method=None, path=None, agent='Swift', newenv = {} for name in ('eventlet.posthooks', 'HTTP_USER_AGENT', 'HTTP_HOST', 'PATH_INFO', 'QUERY_STRING', 'REMOTE_USER', 'REQUEST_METHOD', - 'SCRIPT_NAME', 'SERVER_NAME', 'SERVER_PORT', + 'SCRIPT_NAME', 'SERVER_NAME', 'SERVER_PORT', 'HTTP_ORIGIN', 'SERVER_PROTOCOL', 'swift.cache', 'swift.source', 'swift.trans_id'): if name in env: diff --git a/test/unit/common/middleware/test_formpost.py b/test/unit/common/middleware/test_formpost.py index 278fdbd012..8d7538cd2c 100644 --- a/test/unit/common/middleware/test_formpost.py +++ b/test/unit/common/middleware/test_formpost.py @@ -1223,6 +1223,33 @@ class TestFormPost(unittest.TestCase): self.assertEquals(self.app.requests[0].headers['User-Agent'], 'FormPost') + def test_formpost_with_origin(self): + key = 'abc' + sig, env, body = self._make_sig_env_body( + '/v1/AUTH_test/container', 'http://redirect', 1024, 10, + int(time() + 86400), key, user_agent=False) + env['wsgi.input'] = StringIO('\r\n'.join(body)) + env['swift.account/AUTH_test'] = self._fake_cache_env( + 'AUTH_test', [key]) + env['HTTP_ORIGIN'] = 'http://localhost:5000' + self.app = FakeApp(iter([('201 Created', {}, ''), + ('201 Created', + {'Access-Control-Allow-Origin': + 'http://localhost:5000'}, '')])) + self.auth = tempauth.filter_factory({})(self.app) + self.formpost = formpost.filter_factory({})(self.auth) + + headers = {} + + def start_response(s, h, e=None): + for k, v in h: + headers[k] = v + pass + + body = ''.join(self.formpost(env, start_response)) + self.assertEquals(headers['Access-Control-Allow-Origin'], + 'http://localhost:5000') + def test_formpost_with_multiple_keys(self): key = 'ernie' sig, env, body = self._make_sig_env_body(