form post over XMLHttpRequest (cors) broken

Change-Id: Ia55e0d3974a96e11d49ab3cb26b6dcd7129b5cc8
This commit is contained in:
David Goetz
2013-10-28 17:19:18 +00:00
parent 7ccde73974
commit 56c902c8de
3 changed files with 50 additions and 14 deletions

View File

@@ -49,11 +49,12 @@ different users' uploads, such as::
Note the form method must be POST and the enctype must be set as Note the form method must be POST and the enctype must be set as
"multipart/form-data". "multipart/form-data".
The redirect attribute is the URL to redirect the browser to after The redirect attribute is the URL to redirect the browser to after the upload
the upload completes. The URL will have status and message query completes. This is an optional parameter. If you are uploading the form via an
parameters added to it, indicating the HTTP status code for the XMLHttpRequest the redirect should not be included. The URL will have status
upload (2xx is success) and a possible message for further and message query parameters added to it, indicating the HTTP status code for
information if there was an error (such as "max_file_size exceeded"). 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 The max_file_size attribute must be included and indicates the
largest single file upload that can be done, in bytes. 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 hashlib import sha1
from time import time from time import time
path = '/v1/account/container/object_prefix' 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_size = 104857600
max_file_count = 10 max_file_count = 10
expires = int(time() + 600) expires = int(time() + 600)
@@ -350,6 +351,7 @@ class FormPost(object):
keys = self._get_keys(env) keys = self._get_keys(env)
status = message = '' status = message = ''
attributes = {} attributes = {}
subheaders = []
file_count = 0 file_count = 0
for fp in _iter_requests(env['wsgi.input'], boundary): for fp in _iter_requests(env['wsgi.input'], boundary):
hdrs = rfc822.Message(fp, 0) hdrs = rfc822.Message(fp, 0)
@@ -368,8 +370,8 @@ class FormPost(object):
if 'content-type' not in attributes and 'content-type' in hdrs: if 'content-type' not in attributes and 'content-type' in hdrs:
attributes['content-type'] = \ attributes['content-type'] = \
hdrs['Content-Type'] or 'application/octet-stream' hdrs['Content-Type'] or 'application/octet-stream'
status, message = self._perform_subrequest(env, attributes, fp, status, subheaders, message = \
keys) self._perform_subrequest(env, attributes, fp, keys)
if status[:1] != '2': if status[:1] != '2':
break break
else: else:
@@ -388,13 +390,17 @@ class FormPost(object):
if not status: if not status:
status = '400 Bad Request' status = '400 Bad Request'
message = 'no files to process' message = 'no files to process'
headers = [(k, v) for k, v in subheaders
if k.lower().startswith('access-control')]
redirect = attributes.get('redirect') redirect = attributes.get('redirect')
if not redirect: if not redirect:
body = status body = status
if message: if message:
body = status + '\r\nFormPost: ' + message.title() body = status + '\r\nFormPost: ' + message.title()
headers = [('Content-Type', 'text/plain'), headers.extend([('Content-Type', 'text/plain'),
('Content-Length', len(body))] ('Content-Length', len(body))])
return status, headers, body return status, headers, body
status = status.split(' ', 1)[0] status = status.split(' ', 1)[0]
if '?' in redirect: if '?' in redirect:
@@ -404,7 +410,8 @@ class FormPost(object):
redirect += 'status=%s&message=%s' % (quote(status), quote(message)) redirect += 'status=%s&message=%s' % (quote(status), quote(message))
body = '<html><body><p><a href="%s">' \ body = '<html><body><p><a href="%s">' \
'Click to continue...</a></p></body></html>' % redirect 'Click to continue...</a></p></body></html>' % redirect
headers = [('Location', redirect), ('Content-Length', str(len(body)))] headers.extend(
[('Location', redirect), ('Content-Length', str(len(body)))])
return '303 See Other', headers, body return '303 See Other', headers, body
def _perform_subrequest(self, orig_env, attributes, fp, keys): 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 attributes: dict of the attributes of the form so far.
:param fp: The file-like object containing the request body. :param fp: The file-like object containing the request body.
:param keys: The account keys to validate the signature with. :param keys: The account keys to validate the signature with.
:returns: (status_line, message) :returns: (status_line, headers_list, message)
""" """
if not keys: if not keys:
raise FormUnauthorized('invalid signature') raise FormUnauthorized('invalid signature')
@@ -461,16 +468,18 @@ class FormPost(object):
raise FormUnauthorized('invalid signature') raise FormUnauthorized('invalid signature')
substatus = [None] substatus = [None]
subheaders = [None]
def _start_response(status, headers, exc_info=None): def _start_response(status, headers, exc_info=None):
substatus[0] = status substatus[0] = status
subheaders[0] = headers
i = iter(self.app(subenv, _start_response)) i = iter(self.app(subenv, _start_response))
try: try:
i.next() i.next()
except StopIteration: except StopIteration:
pass pass
return substatus[0], '' return substatus[0], subheaders[0], ''
def _get_keys(self, env): def _get_keys(self, env):
""" """

View File

@@ -485,7 +485,7 @@ def make_pre_authed_env(env, method=None, path=None, agent='Swift',
newenv = {} newenv = {}
for name in ('eventlet.posthooks', 'HTTP_USER_AGENT', 'HTTP_HOST', for name in ('eventlet.posthooks', 'HTTP_USER_AGENT', 'HTTP_HOST',
'PATH_INFO', 'QUERY_STRING', 'REMOTE_USER', 'REQUEST_METHOD', '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', 'SERVER_PROTOCOL', 'swift.cache', 'swift.source',
'swift.trans_id'): 'swift.trans_id'):
if name in env: if name in env:

View File

@@ -1223,6 +1223,33 @@ class TestFormPost(unittest.TestCase):
self.assertEquals(self.app.requests[0].headers['User-Agent'], self.assertEquals(self.app.requests[0].headers['User-Agent'],
'FormPost') '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): def test_formpost_with_multiple_keys(self):
key = 'ernie' key = 'ernie'
sig, env, body = self._make_sig_env_body( sig, env, body = self._make_sig_env_body(