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
"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 = '<html><body><p><a href="%s">' \
'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
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):
"""

View File

@ -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:

View File

@ -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(