From 5105b5336e2874fd5e293b7621312a0af2ee4589 Mon Sep 17 00:00:00 2001 From: Dieter Maurer Date: Tue, 7 Nov 2023 08:07:54 +0100 Subject: [PATCH] support form data in PUT requests (#1183) * support form data in PUT requests * fix tests --- CHANGES.rst | 3 +++ src/ZPublisher/HTTPRequest.py | 25 ++++++++++++++++++------- src/ZPublisher/tests/testHTTPRequest.py | 18 ++++++++++++++++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 0a3cd08e0d..9665d0317b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,9 @@ https://github.com/zopefoundation/Zope/blob/4.x/CHANGES.rst 5.8.7 (unreleased) ------------------ +- Support form data in ``PUT`` requests (following the ``multipart`` example). + Fixes `#1182 `_. + - Separate ZODB connection information into new ZODB Connections view. - Move the cache detail links to the individual database pages. diff --git a/src/ZPublisher/HTTPRequest.py b/src/ZPublisher/HTTPRequest.py index 20421a14b5..9a497839e1 100644 --- a/src/ZPublisher/HTTPRequest.py +++ b/src/ZPublisher/HTTPRequest.py @@ -1432,19 +1432,30 @@ def __init__(self, fp, environ): url_qs = environ.get("QUERY_STRING", "") post_qs = "" hl = [] - content_type = environ.get("CONTENT_TYPE", - "application/x-www-form-urlencoded") - hl.append(("content-type", content_type)) + content_type = environ.get("CONTENT_TYPE") + if content_type is not None: + hl.append(("content-type", content_type)) + else: + content_type = "" content_type, options = parse_options_header(content_type) content_type = content_type.lower() content_disposition = environ.get("CONTENT_DISPOSITION") if content_disposition is not None: hl.append(("content-disposition", content_disposition)) + # Note: ``headers`` does not reflect the complete headers. + # Likely, it should get removed altogether and accesses be replaced + # by a lookup of the corresponding CGI environment keys. self.headers = Headers(hl) parts = () - if method == "POST" \ - and content_type in \ - ("multipart/form-data", "application/x-www-form-urlencoded"): + if method in ("POST", "PUT") \ + and content_type in ( + "multipart/form-data", "application/x-www-form-urlencoded", + "application/x-url-encoded", + # ``Testing`` assumes "application/x-www-form-urlencoded" + # as default content type + # We have mapped a missing content type to ``""``. + "", + ): try: fpos = fp.tell() except Exception: @@ -1456,7 +1467,7 @@ def __init__(self, fp, environ): disk_limit=FORM_DISK_LIMIT, memfile_limit=FORM_MEMFILE_LIMIT, charset="latin-1").parts() - elif content_type == "application/x-www-form-urlencoded": + else: post_qs = fp.read(FORM_MEMORY_LIMIT).decode("latin-1") if fp.read(1): raise BadRequest("form data processing " diff --git a/src/ZPublisher/tests/testHTTPRequest.py b/src/ZPublisher/tests/testHTTPRequest.py index d7fe18e4d3..7b11317794 100644 --- a/src/ZPublisher/tests/testHTTPRequest.py +++ b/src/ZPublisher/tests/testHTTPRequest.py @@ -1468,6 +1468,7 @@ def test_put_with_body_limit(self): "SERVER_NAME": "localhost", "SERVER_PORT": "8080", "REQUEST_METHOD": "PUT", + "CONTENT_TYPE": "application", }, None, ) @@ -1514,6 +1515,23 @@ def test_form_charset(self): self.assertEqual(req["x"], "äöü") self.assertEqual(req["y"], "äöü") + def test_put_with_form(self): + req_factory = self._getTargetClass() + body = b"foo=foo" + req = req_factory( + BytesIO(body), + { + "SERVER_NAME": "localhost", + "SERVER_PORT": "8080", + "REQUEST_METHOD": "PUT", + "CONTENT_TYPE": "application/x-www-form-urlencoded", + "CONTENT_LENGTH": len(body), + }, + None, + ) + req.processInputs() + self.assertEqual(req.form["foo"], "foo") + class TestHTTPRequestZope3Views(TestRequestViewsBase):