python-multipart 0.0.18__tar.gz → 0.0.20__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_multipart-0.0.18 → python_multipart-0.0.20}/CHANGELOG.md +8 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/PKG-INFO +4 -3
- {python_multipart-0.0.18 → python_multipart-0.0.20}/pyproject.toml +1 -1
- {python_multipart-0.0.18 → python_multipart-0.0.20}/python_multipart/__init__.py +1 -1
- {python_multipart-0.0.18 → python_multipart-0.0.20}/python_multipart/multipart.py +24 -4
- python_multipart-0.0.20/tests/test_data/http/empty_message.http +1 -0
- python_multipart-0.0.20/tests/test_data/http/empty_message.yaml +2 -0
- python_multipart-0.0.20/tests/test_data/http/empty_message_with_bad_end.http +1 -0
- python_multipart-0.0.20/tests/test_data/http/empty_message_with_bad_end.yaml +3 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_multipart.py +26 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/.gitignore +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/LICENSE.txt +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/README.md +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/multipart/__init__.py +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/multipart/decoders.py +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/multipart/exceptions.py +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/multipart/multipart.py +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/python_multipart/decoders.py +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/python_multipart/exceptions.py +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/python_multipart/py.typed +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/__init__.py +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/compat.py +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/CRLF_in_header.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/CRLF_in_header.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/CR_in_header.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/CR_in_header.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/CR_in_header_value.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/CR_in_header_value.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary_without_CR.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary_without_CR.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary_without_LF.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary_without_LF.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary_without_final_hyphen.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary_without_final_hyphen.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/bad_end_of_headers.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/bad_end_of_headers.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/bad_header_char.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/bad_header_char.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/bad_initial_boundary.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/bad_initial_boundary.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/base64_encoding.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/base64_encoding.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/empty_header.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/empty_header.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/header_with_number.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/header_with_number.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/mixed_plain_and_base64_encoding.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/mixed_plain_and_base64_encoding.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/multiple_fields.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/multiple_fields.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/multiple_files.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/multiple_files.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/quoted_printable_encoding.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/quoted_printable_encoding.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_field.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_field.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_field_blocks.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_field_blocks.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_field_longer.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_field_longer.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_field_single_file.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_field_single_file.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_field_with_leading_newlines.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_field_with_leading_newlines.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_file.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_file.yaml +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/utf8_filename.http +0 -0
- {python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/utf8_filename.yaml +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.20 (2024-12-16)
|
|
4
|
+
|
|
5
|
+
* Handle messages containing only end boundary [#142](https://github.com/Kludex/python-multipart/pull/142).
|
|
6
|
+
|
|
7
|
+
## 0.0.19 (2024-11-30)
|
|
8
|
+
|
|
9
|
+
* Don't warn when CRLF is found after last boundary on `MultipartParser` [#193](https://github.com/Kludex/python-multipart/pull/193).
|
|
10
|
+
|
|
3
11
|
## 0.0.18 (2024-11-28)
|
|
4
12
|
|
|
5
13
|
* Hard break if found data after last boundary on `MultipartParser` [#189](https://github.com/Kludex/python-multipart/pull/189).
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-multipart
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.20
|
|
4
4
|
Summary: A streaming multipart parser for Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/Kludex/python-multipart
|
|
6
6
|
Project-URL: Documentation, https://kludex.github.io/python-multipart/
|
|
7
7
|
Project-URL: Changelog, https://github.com/Kludex/python-multipart/blob/master/CHANGELOG.md
|
|
8
8
|
Project-URL: Source, https://github.com/Kludex/python-multipart
|
|
9
9
|
Author-email: Andrew Dunham <andrew@du.nham.ca>, Marcelo Trylesinski <marcelotryle@gmail.com>
|
|
10
|
-
License: Apache-2.0
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
License-File: LICENSE.txt
|
|
11
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
12
13
|
Classifier: Environment :: Web Environment
|
|
13
14
|
Classifier: Intended Audience :: Developers
|
|
@@ -130,7 +130,8 @@ class MultipartState(IntEnum):
|
|
|
130
130
|
PART_DATA_START = 8
|
|
131
131
|
PART_DATA = 9
|
|
132
132
|
PART_DATA_END = 10
|
|
133
|
-
|
|
133
|
+
END_BOUNDARY = 11
|
|
134
|
+
END = 12
|
|
134
135
|
|
|
135
136
|
|
|
136
137
|
# Flags for the multipart parser.
|
|
@@ -1119,7 +1120,10 @@ class MultipartParser(BaseParser):
|
|
|
1119
1120
|
# Check to ensure that the last 2 characters in our boundary
|
|
1120
1121
|
# are CRLF.
|
|
1121
1122
|
if index == len(boundary) - 2:
|
|
1122
|
-
if c
|
|
1123
|
+
if c == HYPHEN:
|
|
1124
|
+
# Potential empty message.
|
|
1125
|
+
state = MultipartState.END_BOUNDARY
|
|
1126
|
+
elif c != CR:
|
|
1123
1127
|
# Error!
|
|
1124
1128
|
msg = "Did not find CR at end of boundary (%d)" % (i,)
|
|
1125
1129
|
self.logger.warning(msg)
|
|
@@ -1396,7 +1400,23 @@ class MultipartParser(BaseParser):
|
|
|
1396
1400
|
# the start of the boundary itself.
|
|
1397
1401
|
i -= 1
|
|
1398
1402
|
|
|
1403
|
+
elif state == MultipartState.END_BOUNDARY:
|
|
1404
|
+
if index == len(boundary) - 2 + 1:
|
|
1405
|
+
if c != HYPHEN:
|
|
1406
|
+
msg = "Did not find - at end of boundary (%d)" % (i,)
|
|
1407
|
+
self.logger.warning(msg)
|
|
1408
|
+
e = MultipartParseError(msg)
|
|
1409
|
+
e.offset = i
|
|
1410
|
+
raise e
|
|
1411
|
+
index += 1
|
|
1412
|
+
self.callback("end")
|
|
1413
|
+
state = MultipartState.END
|
|
1414
|
+
|
|
1399
1415
|
elif state == MultipartState.END:
|
|
1416
|
+
# Don't do anything if chunk ends with CRLF.
|
|
1417
|
+
if c == CR and i + 1 < length and data[i + 1] == LF:
|
|
1418
|
+
i += 2
|
|
1419
|
+
continue
|
|
1400
1420
|
# Skip data after the last boundary.
|
|
1401
1421
|
self.logger.warning("Skipping data after last boundary")
|
|
1402
1422
|
i = length
|
|
@@ -1703,8 +1723,8 @@ class FormParser:
|
|
|
1703
1723
|
|
|
1704
1724
|
def _on_end() -> None:
|
|
1705
1725
|
nonlocal writer
|
|
1706
|
-
|
|
1707
|
-
|
|
1726
|
+
if writer is not None:
|
|
1727
|
+
writer.finalize()
|
|
1708
1728
|
if self.on_end is not None:
|
|
1709
1729
|
self.on_end()
|
|
1710
1730
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
----boundary--
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
----boundary-X
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import os
|
|
4
5
|
import random
|
|
5
6
|
import sys
|
|
@@ -9,6 +10,7 @@ from io import BytesIO
|
|
|
9
10
|
from typing import TYPE_CHECKING, cast
|
|
10
11
|
from unittest.mock import Mock
|
|
11
12
|
|
|
13
|
+
import pytest
|
|
12
14
|
import yaml
|
|
13
15
|
|
|
14
16
|
from python_multipart.decoders import Base64Decoder, QuotedPrintableDecoder
|
|
@@ -1248,6 +1250,30 @@ class TestFormParser(unittest.TestCase):
|
|
|
1248
1250
|
f = FormParser("multipart/form-data", on_field=Mock(), on_file=on_file, boundary="boundary")
|
|
1249
1251
|
f.write(data.encode("latin-1"))
|
|
1250
1252
|
|
|
1253
|
+
@pytest.fixture(autouse=True)
|
|
1254
|
+
def inject_fixtures(self, caplog: pytest.LogCaptureFixture) -> None:
|
|
1255
|
+
self._caplog = caplog
|
|
1256
|
+
|
|
1257
|
+
def test_multipart_parser_data_end_with_crlf_without_warnings(self) -> None:
|
|
1258
|
+
"""This test makes sure that the parser does not handle when the data ends with a CRLF."""
|
|
1259
|
+
data = (
|
|
1260
|
+
"--boundary\r\n"
|
|
1261
|
+
'Content-Disposition: form-data; name="file"; filename="filename.txt"\r\n'
|
|
1262
|
+
"Content-Type: text/plain\r\n\r\n"
|
|
1263
|
+
"hello\r\n"
|
|
1264
|
+
"--boundary--\r\n"
|
|
1265
|
+
)
|
|
1266
|
+
|
|
1267
|
+
files: list[File] = []
|
|
1268
|
+
|
|
1269
|
+
def on_file(f: FileProtocol) -> None:
|
|
1270
|
+
files.append(cast(File, f))
|
|
1271
|
+
|
|
1272
|
+
f = FormParser("multipart/form-data", on_field=Mock(), on_file=on_file, boundary="boundary")
|
|
1273
|
+
with self._caplog.at_level(logging.WARNING):
|
|
1274
|
+
f.write(data.encode("latin-1"))
|
|
1275
|
+
assert len(self._caplog.records) == 0
|
|
1276
|
+
|
|
1251
1277
|
def test_max_size_multipart(self) -> None:
|
|
1252
1278
|
# Load test data.
|
|
1253
1279
|
test_file = "single_field_single_file.http"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/CRLF_in_header.http
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/CRLF_in_header.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/CR_in_header_value.http
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/CR_in_header_value.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary.http
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/bad_end_of_headers.http
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/bad_end_of_headers.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/bad_header_char.http
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/bad_header_char.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/bad_initial_boundary.http
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/bad_initial_boundary.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/base64_encoding.http
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/base64_encoding.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/header_with_number.http
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/header_with_number.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/multiple_fields.http
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/multiple_fields.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/multiple_files.http
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/multiple_files.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_field_blocks.http
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_field_blocks.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_field_longer.http
RENAMED
|
File without changes
|
{python_multipart-0.0.18 → python_multipart-0.0.20}/tests/test_data/http/single_field_longer.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|