diff --git a/CHANGES.rst b/CHANGES.rst index 1b7e705..96f2a3f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,15 @@ 7.1 (unreleased) ================ -- Nothing changed yet. +- Fix test suite to use proper line endings (\r\n) in raw multipart/form-data + HTTP requests, because multipart 1.0.0 is stricter about line endings. + Fixes `issue `_. + +- ``FileUpload`` objects now implement a fallback ``seekable()`` method on + Python 3.7 through 3.10, where tempfile.SpooledTemporaryFile lacks it. + Fixes `issue 44 `_ + again, which had regressed due to certain assumptions that were no longer + true after the multipart 1.0.0 release. 7.0 (2023-08-29) diff --git a/src/zope/publisher/browser.py b/src/zope/publisher/browser.py index 3b169a4..51414c1 100644 --- a/src/zope/publisher/browser.py +++ b/src/zope/publisher/browser.py @@ -19,6 +19,7 @@ packaged into a nice, Python-friendly 'FileUpload' object. """ import re +import tempfile from email.message import Message from urllib.parse import parse_qsl @@ -676,6 +677,12 @@ def __init__(self, aFieldStorage): if hasattr(file, m): d[m] = getattr(file, m) + if 'seekable' not in d and isinstance( + file, tempfile.SpooledTemporaryFile + ): # Python 3.7 to 3.10 + # NB: can't assign file._file.seekable, file._file might roll over + d['seekable'] = lambda: file._file.seekable() + self.headers = aFieldStorage.headers filename = aFieldStorage.filename if filename is not None: diff --git a/src/zope/publisher/tests/test_browserrequest.py b/src/zope/publisher/tests/test_browserrequest.py index 5c9d2be..2dcb3db 100644 --- a/src/zope/publisher/tests/test_browserrequest.py +++ b/src/zope/publisher/tests/test_browserrequest.py @@ -43,7 +43,7 @@ Content-Type: application/octet-stream -----------------------------1-- -""" +""".replace(b'\n', b'\r\n') LARGE_FILE_BODY = b''.join([b"""-----------------------------1 Content-Disposition: form-data; name="upload"; filename="test" @@ -51,14 +51,14 @@ Here comes some text! """, (b'test' * 1000), b""" -----------------------------1-- -"""]) +"""]).replace(b'\n', b'\r\n') LARGE_POSTED_VALUE = b''.join([b"""-----------------------------1 Content-Disposition: form-data; name="upload" Here comes some text! """, (b'test' * 1000), b""" -----------------------------1-- -"""]) +"""]).replace(b'\n', b'\r\n') IE_FILE_BODY = b"""-----------------------------1 Content-Disposition: form-data; name="upload"; filename="C:\\Windows\\notepad.exe" @@ -66,7 +66,7 @@ Some data -----------------------------1-- -""" # noqa: E501 line too long +""".replace(b'\n', b'\r\n') # noqa: E501 line too long def publish(request): @@ -307,7 +307,7 @@ def testFormMultipartUTF8(self): 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'multipart/form_data; boundary=-123', } - body = b'\n'.join([ + body = b'\r\n'.join([ b'---123', b'Content-Disposition: form-data; name="a"', b'', @@ -333,7 +333,7 @@ def testFormMultipartFilenameUTF8(self): 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'multipart/form_data; boundary=-123', } - body = b'\n'.join([ + body = b'\r\n'.join([ b'---123', b'Content-Disposition: form-data; name="upload";' b' filename="\xe2\x98\x83"', @@ -355,7 +355,7 @@ def testFormMultipartFilenameLatin7(self): 'CONTENT_TYPE': ( 'multipart/form_data; boundary=-123; charset=ISO-8859-13'), } - body = b'\n'.join([ + body = b'\r\n'.join([ b'---123', b'Content-Disposition: form-data; name="upload";' b' filename="\xc0\xfeuolyno"', @@ -376,7 +376,7 @@ def testFormMultipartFilenameLatin7DefaultCharset(self): 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'multipart/form_data; boundary=-123', } - body = b'\n'.join([ + body = b'\r\n'.join([ b'---123', b'Content-Disposition: form-data; name="upload";' b' filename="\xc0\xfeuolyno"', @@ -681,7 +681,7 @@ def testFormMultipartDuplicateFieldNames(self): 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'multipart/form_data; boundary=-123', } - body = b'\n'.join([ + body = b'\r\n'.join([ b'---123', b'Content-Disposition: form-data; name="a"', b'',