python-multipart 0.0.21__tar.gz → 0.0.23__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.21 → python_multipart-0.0.23}/CHANGELOG.md +10 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/PKG-INFO +3 -2
- {python_multipart-0.0.21 → python_multipart-0.0.23}/multipart/__init__.py +1 -1
- {python_multipart-0.0.21 → python_multipart-0.0.23}/pyproject.toml +13 -11
- {python_multipart-0.0.21 → python_multipart-0.0.23}/python_multipart/__init__.py +1 -5
- {python_multipart-0.0.21 → python_multipart-0.0.23}/python_multipart/multipart.py +5 -9
- python_multipart-0.0.23/tests/test_file.py +26 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_multipart.py +1 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/.gitignore +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/LICENSE.txt +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/README.md +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/multipart/decoders.py +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/multipart/exceptions.py +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/multipart/multipart.py +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/python_multipart/decoders.py +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/python_multipart/exceptions.py +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/python_multipart/py.typed +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/__init__.py +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/compat.py +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/CRLF_in_header.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/CRLF_in_header.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/CR_in_header.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/CR_in_header.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/CR_in_header_value.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/CR_in_header_value.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/almost_match_boundary.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/almost_match_boundary.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/almost_match_boundary_without_CR.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/almost_match_boundary_without_CR.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/almost_match_boundary_without_LF.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/almost_match_boundary_without_LF.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/almost_match_boundary_without_final_hyphen.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/almost_match_boundary_without_final_hyphen.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/bad_end_of_headers.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/bad_end_of_headers.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/bad_header_char.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/bad_header_char.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/bad_initial_boundary.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/bad_initial_boundary.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/base64_encoding.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/base64_encoding.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/empty_header.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/empty_header.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/empty_message.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/empty_message.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/empty_message_with_bad_end.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/empty_message_with_bad_end.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/header_with_number.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/header_with_number.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/mixed_plain_and_base64_encoding.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/mixed_plain_and_base64_encoding.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/multiple_fields.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/multiple_fields.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/multiple_files.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/multiple_files.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/quoted_printable_encoding.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/quoted_printable_encoding.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/single_field.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/single_field.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/single_field_blocks.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/single_field_blocks.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/single_field_longer.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/single_field_longer.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/single_field_single_file.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/single_field_single_file.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/single_field_with_leading_newlines.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/single_field_with_leading_newlines.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/single_file.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/single_file.yaml +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/utf8_filename.http +0 -0
- {python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/utf8_filename.yaml +0 -0
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.23 (2026-04-05)
|
|
4
|
+
|
|
5
|
+
* Remove unused `trust_x_headers` parameter and `X-File-Name` fallback [#196](https://github.com/Kludex/python-multipart/pull/196).
|
|
6
|
+
* Return processed length from `QuerystringParser._internal_write` [#229](https://github.com/Kludex/python-multipart/pull/229).
|
|
7
|
+
* Cleanup metadata dunders from `__init__.py` [#227](https://github.com/Kludex/python-multipart/pull/227).
|
|
8
|
+
|
|
9
|
+
## 0.0.22 (2026-01-25)
|
|
10
|
+
|
|
11
|
+
* Drop directory path from filename in `File` [9433f4b](https://github.com/Kludex/python-multipart/commit/9433f4bbc9652bdde82bbe380984e32f8cfc89c4).
|
|
12
|
+
|
|
3
13
|
## 0.0.21 (2025-12-17)
|
|
4
14
|
|
|
5
15
|
* Add support for Python 3.14 and drop EOL 3.8 and 3.9 [#216](https://github.com/Kludex/python-multipart/pull/216).
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-multipart
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.23
|
|
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
|
-
Author-email: Andrew Dunham <andrew@du.nham.ca
|
|
9
|
+
Author-email: Andrew Dunham <andrew@du.nham.ca>
|
|
10
|
+
Maintainer-email: Marcelo Trylesinski <marcelotryle@gmail.com>
|
|
10
11
|
License-Expression: Apache-2.0
|
|
11
12
|
License-File: LICENSE.txt
|
|
12
13
|
Classifier: Development Status :: 5 - Production/Stable
|
|
@@ -21,4 +21,4 @@ for p in sys.path:
|
|
|
21
21
|
else:
|
|
22
22
|
warnings.warn("Please use `import python_multipart` instead.", PendingDeprecationWarning, stacklevel=2)
|
|
23
23
|
from python_multipart import *
|
|
24
|
-
from python_multipart import __all__,
|
|
24
|
+
from python_multipart import __all__, __version__
|
|
@@ -11,6 +11,8 @@ license = "Apache-2.0"
|
|
|
11
11
|
requires-python = ">=3.10"
|
|
12
12
|
authors = [
|
|
13
13
|
{ name = "Andrew Dunham", email = "andrew@du.nham.ca" },
|
|
14
|
+
]
|
|
15
|
+
maintainers = [
|
|
14
16
|
{ name = "Marcelo Trylesinski", email = "marcelotryle@gmail.com" },
|
|
15
17
|
]
|
|
16
18
|
classifiers = [
|
|
@@ -33,18 +35,18 @@ dependencies = []
|
|
|
33
35
|
[dependency-groups]
|
|
34
36
|
dev = [
|
|
35
37
|
"atomicwrites==1.4.1",
|
|
36
|
-
"attrs==
|
|
37
|
-
"coverage==7.
|
|
38
|
-
"more-itertools==10.
|
|
39
|
-
"pbr==
|
|
40
|
-
"pluggy==1.
|
|
38
|
+
"attrs==26.1.0",
|
|
39
|
+
"coverage==7.13.5",
|
|
40
|
+
"more-itertools==10.8.0",
|
|
41
|
+
"pbr==7.0.3",
|
|
42
|
+
"pluggy==1.6.0",
|
|
41
43
|
"py==1.11.0",
|
|
42
|
-
"pytest==
|
|
43
|
-
"pytest-cov==
|
|
44
|
-
"PyYAML==6.0.
|
|
45
|
-
"invoke==2.2.
|
|
46
|
-
"pytest-timeout==2.
|
|
47
|
-
"ruff==0.
|
|
44
|
+
"pytest==9.0.2",
|
|
45
|
+
"pytest-cov==7.1.0",
|
|
46
|
+
"PyYAML==6.0.3",
|
|
47
|
+
"invoke==2.2.1",
|
|
48
|
+
"pytest-timeout==2.4.0",
|
|
49
|
+
"ruff==0.15.8",
|
|
48
50
|
"mypy",
|
|
49
51
|
"types-PyYAML",
|
|
50
52
|
"atheris==2.3.0; python_version <= '3.11'",
|
|
@@ -375,7 +375,9 @@ class File:
|
|
|
375
375
|
|
|
376
376
|
# Split the extension from the filename.
|
|
377
377
|
if file_name is not None:
|
|
378
|
-
|
|
378
|
+
# Extract just the basename to avoid directory traversal
|
|
379
|
+
basename = os.path.basename(file_name)
|
|
380
|
+
base, ext = os.path.splitext(basename)
|
|
379
381
|
self._file_base = base
|
|
380
382
|
self._ext = ext
|
|
381
383
|
|
|
@@ -934,7 +936,7 @@ class QuerystringParser(BaseParser):
|
|
|
934
936
|
|
|
935
937
|
self.state = state
|
|
936
938
|
self._found_sep = found_sep
|
|
937
|
-
return
|
|
939
|
+
return length
|
|
938
940
|
|
|
939
941
|
def finalize(self) -> None:
|
|
940
942
|
"""Finalize this parser, which signals to that we are finished parsing,
|
|
@@ -1782,7 +1784,6 @@ def create_form_parser(
|
|
|
1782
1784
|
headers: dict[str, bytes],
|
|
1783
1785
|
on_field: OnFieldCallback | None,
|
|
1784
1786
|
on_file: OnFileCallback | None,
|
|
1785
|
-
trust_x_headers: bool = False,
|
|
1786
1787
|
config: dict[Any, Any] = {},
|
|
1787
1788
|
) -> FormParser:
|
|
1788
1789
|
"""This function is a helper function to aid in creating a FormParser
|
|
@@ -1795,8 +1796,6 @@ def create_form_parser(
|
|
|
1795
1796
|
headers: A dictionary-like object of HTTP headers. The only required header is Content-Type.
|
|
1796
1797
|
on_field: Callback to call with each parsed field.
|
|
1797
1798
|
on_file: Callback to call with each parsed file.
|
|
1798
|
-
trust_x_headers: Whether or not to trust information received from certain X-Headers - for example, the file
|
|
1799
|
-
name from X-File-Name.
|
|
1800
1799
|
config: Configuration variables to pass to the FormParser.
|
|
1801
1800
|
"""
|
|
1802
1801
|
content_type: str | bytes | None = headers.get("Content-Type")
|
|
@@ -1812,11 +1811,8 @@ def create_form_parser(
|
|
|
1812
1811
|
# We need content_type to be a string, not a bytes object.
|
|
1813
1812
|
content_type = content_type.decode("latin-1")
|
|
1814
1813
|
|
|
1815
|
-
# File names are optional.
|
|
1816
|
-
file_name = headers.get("X-File-Name")
|
|
1817
|
-
|
|
1818
1814
|
# Instantiate a form parser.
|
|
1819
|
-
form_parser = FormParser(content_type, on_field, on_file, boundary=boundary,
|
|
1815
|
+
form_parser = FormParser(content_type, on_field, on_file, boundary=boundary, config=config)
|
|
1820
1816
|
|
|
1821
1817
|
# Return our parser.
|
|
1822
1818
|
return form_parser
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from python_multipart.multipart import File
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_upload_dir_with_leading_slash_in_filename(tmp_path: Path) -> None:
|
|
7
|
+
upload_dir = tmp_path / "upload"
|
|
8
|
+
upload_dir.mkdir()
|
|
9
|
+
|
|
10
|
+
# When the file_name provided has a leading slash, we should only use the basename.
|
|
11
|
+
# This is to avoid directory traversal.
|
|
12
|
+
to_upload = tmp_path / "foo.txt"
|
|
13
|
+
|
|
14
|
+
file = File(
|
|
15
|
+
bytes(to_upload),
|
|
16
|
+
config={
|
|
17
|
+
"UPLOAD_DIR": bytes(upload_dir),
|
|
18
|
+
"UPLOAD_KEEP_FILENAME": True,
|
|
19
|
+
"UPLOAD_KEEP_EXTENSIONS": True,
|
|
20
|
+
"MAX_MEMORY_FILE_SIZE": 10,
|
|
21
|
+
},
|
|
22
|
+
)
|
|
23
|
+
file.write(b"123456789012")
|
|
24
|
+
assert not file.in_memory
|
|
25
|
+
assert Path(upload_dir / "foo.txt").exists()
|
|
26
|
+
assert Path(upload_dir / "foo.txt").read_bytes() == b"123456789012"
|
|
@@ -1371,6 +1371,7 @@ class TestFormParser(unittest.TestCase):
|
|
|
1371
1371
|
self.assertEqual(calls, 3)
|
|
1372
1372
|
|
|
1373
1373
|
|
|
1374
|
+
@parametrize_class
|
|
1374
1375
|
class TestHelperFunctions(unittest.TestCase):
|
|
1375
1376
|
def test_create_form_parser(self) -> None:
|
|
1376
1377
|
r = create_form_parser({"Content-Type": b"application/octet-stream"}, None, None)
|
|
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.21 → python_multipart-0.0.23}/tests/test_data/http/CRLF_in_header.http
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/CRLF_in_header.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/CR_in_header_value.http
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/CR_in_header_value.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/almost_match_boundary.http
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/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.21 → python_multipart-0.0.23}/tests/test_data/http/bad_end_of_headers.http
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/bad_end_of_headers.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/bad_header_char.http
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/bad_header_char.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/bad_initial_boundary.http
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/bad_initial_boundary.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/base64_encoding.http
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/base64_encoding.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.21 → python_multipart-0.0.23}/tests/test_data/http/header_with_number.http
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/header_with_number.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/multiple_fields.http
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/multiple_fields.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/multiple_files.http
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/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.21 → python_multipart-0.0.23}/tests/test_data/http/single_field_blocks.http
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/single_field_blocks.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/tests/test_data/http/single_field_longer.http
RENAMED
|
File without changes
|
{python_multipart-0.0.21 → python_multipart-0.0.23}/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
|