Fix a formpost reponse bug

The formpost middleware turns POST requests into object PUT requests.
This request goes through other middlewares.
Even if the user requests an object upload using formpost, the user should be able to know the contents of the error response that occurred in other middleware.
However, in formpost, using only the status received by the _perform_subrequest() method, the error response body is altered and returned to the user (e.g., `400 Bad Request`), so it is difficult for the user to know why the error occurred.
Therefore, not only status but also response should be added to the return value of _perform_subrequest(), and if there is an error response body, it should be delivered to the user without altering it.

Closes-Bug: #1990942
Change-Id: I4020e90dfaf7370a7941d123e9bea920c09b1aa0
This commit is contained in:
dongu 2022-09-30 14:09:27 +09:00
parent fa19868703
commit 326e68d70d
2 changed files with 48 additions and 10 deletions

View File

@ -136,10 +136,11 @@ from swift.common.digest import get_allowed_digests, \
extract_digest_and_algorithm, DEFAULT_ALLOWED_DIGESTS
from swift.common.utils import streq_const_time, parse_content_disposition, \
parse_mime_headers, iter_multipart_mime_documents, reiterate, \
close_if_possible, get_logger
closing_if_possible, get_logger
from swift.common.registry import register_swift_info
from swift.common.wsgi import make_pre_authed_env
from swift.common.swob import HTTPUnauthorized, wsgi_to_str, str_to_wsgi
from swift.common.http import is_success
from swift.proxy.controllers.base import get_account_info, get_container_info
@ -278,6 +279,7 @@ class FormPost(object):
attributes = {}
file_attributes = {}
subheaders = []
resp_body = None
file_count = 0
for fp in iter_multipart_mime_documents(
env['wsgi.input'], boundary, read_chunk_size=READ_CHUNK_SIZE):
@ -302,9 +304,10 @@ class FormPost(object):
'content-encoding' in hdrs:
file_attributes['content-encoding'] = \
hdrs['Content-Encoding']
status, subheaders = \
status, subheaders, resp_body = \
self._perform_subrequest(env, file_attributes, fp, keys)
if not status.startswith('2'):
status_code = int(status.split(' ', 1)[0])
if not is_success(status_code):
break
else:
data = b''
@ -325,6 +328,7 @@ class FormPost(object):
status = '400 Bad Request'
message = 'no files to process'
status_code = int(status.split(' ', 1)[0])
headers = [(k, v) for k, v in subheaders
if k.lower().startswith('access-control')]
@ -333,17 +337,19 @@ class FormPost(object):
body = status
if message:
body = status + '\r\nFormPost: ' + message.title()
headers.extend([('Content-Type', 'text/plain'),
('Content-Length', len(body))])
if six.PY3:
body = body.encode('utf-8')
if not is_success(status_code) and resp_body:
body = resp_body
headers.extend([('Content-Type', 'text/plain'),
('Content-Length', len(body))])
return status, headers, body
status = status.split(' ', 1)[0]
if '?' in redirect:
redirect += '&'
else:
redirect += '?'
redirect += 'status=%s&message=%s' % (quote(status), quote(message))
redirect += 'status=%s&message=%s' % (quote(str(status_code)),
quote(message))
body = '<html><body><p><a href="%s">' \
'Click to continue...</a></p></body></html>' % redirect
if six.PY3:
@ -447,8 +453,10 @@ class FormPost(object):
# reiterate to ensure the response started,
# but drop any data on the floor
close_if_possible(reiterate(self.app(subenv, _start_response)))
return substatus[0], subheaders[0]
resp = self.app(subenv, _start_response)
with closing_if_possible(reiterate(resp)):
body = b''.join(resp)
return substatus[0], subheaders[0], body
def _get_keys(self, env):
"""

View File

@ -1046,7 +1046,7 @@ class TestFormPost(unittest.TestCase):
self.assertTrue(b'201 Created' in body)
self.assertEqual(len(self.app.requests), 2)
def test_subrequest_fails(self):
def test_subrequest_fails_redirect_404(self):
key = b'abc'
sig, env, body = self._make_sig_env_body(
'/v1/AUTH_test/container', 'http://brim.net', 1024, 10,
@ -1083,6 +1083,36 @@ class TestFormPost(unittest.TestCase):
self.assertTrue(b'http://brim.net?status=404&message=' in body)
self.assertEqual(len(self.app.requests), 1)
def test_subrequest_fails_no_redirect_503(self):
key = b'abc'
sig, env, body = self._make_sig_env_body(
'/v1/AUTH_test/container', '', 1024, 10,
int(time() + 86400), key)
env['wsgi.input'] = BytesIO(b'\r\n'.join(body))
env['swift.infocache'][get_cache_key('AUTH_test')] = (
self._fake_cache_env('AUTH_test', [key]))
env['swift.infocache'][get_cache_key(
'AUTH_test', 'container')] = {'meta': {}}
self.app = FakeApp(iter([('503 Server Error', {}, b'some bad news')]))
self.auth = tempauth.filter_factory({})(self.app)
self.formpost = formpost.filter_factory({})(self.auth)
status = [None]
headers = [None]
exc_info = [None]
def start_response(s, h, e=None):
status[0] = s
headers[0] = h
exc_info[0] = e
body = b''.join(self.formpost(env, start_response))
status = status[0]
headers = headers[0]
exc_info = exc_info[0]
self.assertEqual(status, '503 Server Error')
self.assertTrue(b'bad news' in body)
self.assertEqual(len(self.app.requests), 1)
def test_truncated_attr_value(self):
key = b'abc'
redirect = 'a' * formpost.MAX_VALUE_LENGTH