mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
Fixed #34968 -- Made multipart parsing of headers raise an error on too long headers.
This also allow customizing the maximum size of headers via MAX_TOTAL_HEADER_SIZE.
This commit is contained in:
parent
5e28cd3f2c
commit
1c6e8ec4ed
@ -41,6 +41,7 @@ RAW = "raw"
|
||||
FILE = "file"
|
||||
FIELD = "field"
|
||||
FIELD_TYPES = frozenset([FIELD, RAW])
|
||||
MAX_TOTAL_HEADER_SIZE = 1024
|
||||
|
||||
|
||||
class MultiPartParser:
|
||||
@ -682,21 +683,30 @@ def parse_boundary_stream(stream, max_header_size):
|
||||
"""
|
||||
Parse one and exactly one stream that encapsulates a boundary.
|
||||
"""
|
||||
# Stream at beginning of header, look for end of header
|
||||
# and parse it if found. The header must fit within one
|
||||
# chunk.
|
||||
chunk = stream.read(max_header_size)
|
||||
|
||||
# 'find' returns the top of these four bytes, so we'll
|
||||
# need to munch them later to prevent them from polluting
|
||||
# the payload.
|
||||
header_end = chunk.find(b"\r\n\r\n")
|
||||
# Look for the end of headers and if not found extend the search to double
|
||||
# the size up to the MAX_TOTAL_HEADER_SIZE.
|
||||
headers_chunk_size = 1024
|
||||
while True:
|
||||
if headers_chunk_size > max_header_size:
|
||||
raise MultiPartParserError("Request max total header size exceeded.")
|
||||
|
||||
if header_end == -1:
|
||||
# we find no header, so we just mark this fact and pass on
|
||||
# the stream verbatim
|
||||
# Stream at beginning of header, look for end of header and parse it if
|
||||
# found. The header must fit within one chunk.
|
||||
chunk = stream.read(headers_chunk_size)
|
||||
# 'find' returns the top of these four bytes, so munch them later to
|
||||
# prevent them from polluting the payload.
|
||||
header_end = chunk.find(b"\r\n\r\n")
|
||||
if header_end != -1:
|
||||
break
|
||||
|
||||
# Find no header, mark this fact and pass on the stream verbatim.
|
||||
stream.unget(chunk)
|
||||
return (RAW, {}, stream)
|
||||
# No more data to read.
|
||||
if len(chunk) < headers_chunk_size:
|
||||
return (RAW, {}, stream)
|
||||
# Double the chunk size.
|
||||
headers_chunk_size *= 2
|
||||
|
||||
header = chunk[:header_end]
|
||||
|
||||
@ -740,4 +750,4 @@ class Parser:
|
||||
boundarystream = InterBoundaryIter(self._stream, self._separator)
|
||||
for sub_stream in boundarystream:
|
||||
# Iterate over each part
|
||||
yield parse_boundary_stream(sub_stream, 1024)
|
||||
yield parse_boundary_stream(sub_stream, MAX_TOTAL_HEADER_SIZE)
|
||||
|
@ -16,6 +16,7 @@ from django.core.files.storage import default_storage
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile, UploadedFile
|
||||
from django.http.multipartparser import (
|
||||
FILE,
|
||||
MAX_TOTAL_HEADER_SIZE,
|
||||
MultiPartParser,
|
||||
MultiPartParserError,
|
||||
Parser,
|
||||
@ -603,6 +604,57 @@ class FileUploadTests(TestCase):
|
||||
temp_path = response.json()["temp_path"]
|
||||
self.assertIs(os.path.exists(temp_path), False)
|
||||
|
||||
def test_upload_large_header_fields(self):
|
||||
payload = client.FakePayload(
|
||||
"\r\n".join(
|
||||
[
|
||||
"--" + client.BOUNDARY,
|
||||
'Content-Disposition: form-data; name="my_file"; '
|
||||
'filename="test.txt"',
|
||||
"Content-Type: text/plain",
|
||||
"X-Long-Header: %s" % ("-" * 500),
|
||||
"",
|
||||
"file contents",
|
||||
"--" + client.BOUNDARY + "--\r\n",
|
||||
]
|
||||
),
|
||||
)
|
||||
r = {
|
||||
"CONTENT_LENGTH": len(payload),
|
||||
"CONTENT_TYPE": client.MULTIPART_CONTENT,
|
||||
"PATH_INFO": "/echo_content/",
|
||||
"REQUEST_METHOD": "POST",
|
||||
"wsgi.input": payload,
|
||||
}
|
||||
response = self.client.request(**r)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json(), {"my_file": "file contents"})
|
||||
|
||||
def test_upload_header_fields_too_large(self):
|
||||
payload = client.FakePayload(
|
||||
"\r\n".join(
|
||||
[
|
||||
"--" + client.BOUNDARY,
|
||||
'Content-Disposition: form-data; name="my_file"; '
|
||||
'filename="test.txt"',
|
||||
"Content-Type: text/plain",
|
||||
"X-Long-Header: %s" % ("-" * (MAX_TOTAL_HEADER_SIZE + 1)),
|
||||
"",
|
||||
"file contents",
|
||||
"--" + client.BOUNDARY + "--\r\n",
|
||||
]
|
||||
),
|
||||
)
|
||||
r = {
|
||||
"CONTENT_LENGTH": len(payload),
|
||||
"CONTENT_TYPE": client.MULTIPART_CONTENT,
|
||||
"PATH_INFO": "/echo_content/",
|
||||
"REQUEST_METHOD": "POST",
|
||||
"wsgi.input": payload,
|
||||
}
|
||||
response = self.client.request(**r)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_fileupload_getlist(self):
|
||||
file = tempfile.NamedTemporaryFile
|
||||
with file() as file1, file() as file2, file() as file2a:
|
||||
|
@ -11,7 +11,7 @@ from django.http import (
|
||||
RawPostDataException,
|
||||
UnreadablePostError,
|
||||
)
|
||||
from django.http.multipartparser import MultiPartParserError
|
||||
from django.http.multipartparser import MAX_TOTAL_HEADER_SIZE, MultiPartParserError
|
||||
from django.http.request import split_domain_port
|
||||
from django.test import RequestFactory, SimpleTestCase, override_settings
|
||||
from django.test.client import BOUNDARY, MULTIPART_CONTENT, FakePayload
|
||||
@ -691,6 +691,31 @@ class RequestsTests(SimpleTestCase):
|
||||
with self.assertRaisesMessage(MultiPartParserError, msg):
|
||||
request.POST
|
||||
|
||||
def test_multipart_with_header_fields_too_large(self):
|
||||
payload = FakePayload(
|
||||
"\r\n".join(
|
||||
[
|
||||
"--boundary",
|
||||
'Content-Disposition: form-data; name="name"',
|
||||
"X-Long-Header: %s" % ("-" * (MAX_TOTAL_HEADER_SIZE + 1)),
|
||||
"",
|
||||
"value",
|
||||
"--boundary--",
|
||||
]
|
||||
)
|
||||
)
|
||||
request = WSGIRequest(
|
||||
{
|
||||
"REQUEST_METHOD": "POST",
|
||||
"CONTENT_TYPE": "multipart/form-data; boundary=boundary",
|
||||
"CONTENT_LENGTH": len(payload),
|
||||
"wsgi.input": payload,
|
||||
}
|
||||
)
|
||||
msg = "Request max total header size exceeded."
|
||||
with self.assertRaisesMessage(MultiPartParserError, msg):
|
||||
request.POST
|
||||
|
||||
def test_POST_connection_error(self):
|
||||
"""
|
||||
If wsgi.input.read() raises an exception while trying to read() the
|
||||
|
Loading…
Reference in New Issue
Block a user