python-multipart 0.0.16__tar.gz → 0.0.18__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.
Files changed (67) hide show
  1. {python_multipart-0.0.16 → python_multipart-0.0.18}/CHANGELOG.md +8 -0
  2. {python_multipart-0.0.16 → python_multipart-0.0.18}/PKG-INFO +2 -3
  3. python_multipart-0.0.18/multipart/__init__.py +24 -0
  4. {python_multipart-0.0.16 → python_multipart-0.0.18}/pyproject.toml +2 -2
  5. {python_multipart-0.0.16 → python_multipart-0.0.18}/python_multipart/__init__.py +1 -1
  6. {python_multipart-0.0.16 → python_multipart-0.0.18}/python_multipart/multipart.py +4 -4
  7. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_multipart.py +39 -1
  8. python_multipart-0.0.16/multipart/__init__.py +0 -21
  9. {python_multipart-0.0.16 → python_multipart-0.0.18}/.gitignore +0 -0
  10. {python_multipart-0.0.16 → python_multipart-0.0.18}/LICENSE.txt +0 -0
  11. {python_multipart-0.0.16 → python_multipart-0.0.18}/README.md +0 -0
  12. {python_multipart-0.0.16 → python_multipart-0.0.18}/multipart/decoders.py +0 -0
  13. {python_multipart-0.0.16 → python_multipart-0.0.18}/multipart/exceptions.py +0 -0
  14. {python_multipart-0.0.16 → python_multipart-0.0.18}/multipart/multipart.py +0 -0
  15. {python_multipart-0.0.16 → python_multipart-0.0.18}/python_multipart/decoders.py +0 -0
  16. {python_multipart-0.0.16 → python_multipart-0.0.18}/python_multipart/exceptions.py +0 -0
  17. {python_multipart-0.0.16 → python_multipart-0.0.18}/python_multipart/py.typed +0 -0
  18. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/__init__.py +0 -0
  19. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/compat.py +0 -0
  20. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/CRLF_in_header.http +0 -0
  21. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/CRLF_in_header.yaml +0 -0
  22. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/CR_in_header.http +0 -0
  23. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/CR_in_header.yaml +0 -0
  24. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/CR_in_header_value.http +0 -0
  25. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/CR_in_header_value.yaml +0 -0
  26. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/almost_match_boundary.http +0 -0
  27. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/almost_match_boundary.yaml +0 -0
  28. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/almost_match_boundary_without_CR.http +0 -0
  29. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/almost_match_boundary_without_CR.yaml +0 -0
  30. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/almost_match_boundary_without_LF.http +0 -0
  31. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/almost_match_boundary_without_LF.yaml +0 -0
  32. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/almost_match_boundary_without_final_hyphen.http +0 -0
  33. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/almost_match_boundary_without_final_hyphen.yaml +0 -0
  34. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/bad_end_of_headers.http +0 -0
  35. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/bad_end_of_headers.yaml +0 -0
  36. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/bad_header_char.http +0 -0
  37. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/bad_header_char.yaml +0 -0
  38. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/bad_initial_boundary.http +0 -0
  39. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/bad_initial_boundary.yaml +0 -0
  40. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/base64_encoding.http +0 -0
  41. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/base64_encoding.yaml +0 -0
  42. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/empty_header.http +0 -0
  43. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/empty_header.yaml +0 -0
  44. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/header_with_number.http +0 -0
  45. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/header_with_number.yaml +0 -0
  46. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/mixed_plain_and_base64_encoding.http +0 -0
  47. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/mixed_plain_and_base64_encoding.yaml +0 -0
  48. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/multiple_fields.http +0 -0
  49. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/multiple_fields.yaml +0 -0
  50. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/multiple_files.http +0 -0
  51. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/multiple_files.yaml +0 -0
  52. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/quoted_printable_encoding.http +0 -0
  53. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/quoted_printable_encoding.yaml +0 -0
  54. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/single_field.http +0 -0
  55. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/single_field.yaml +0 -0
  56. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/single_field_blocks.http +0 -0
  57. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/single_field_blocks.yaml +0 -0
  58. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/single_field_longer.http +0 -0
  59. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/single_field_longer.yaml +0 -0
  60. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/single_field_single_file.http +0 -0
  61. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/single_field_single_file.yaml +0 -0
  62. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/single_field_with_leading_newlines.http +0 -0
  63. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/single_field_with_leading_newlines.yaml +0 -0
  64. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/single_file.http +0 -0
  65. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/single_file.yaml +0 -0
  66. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/utf8_filename.http +0 -0
  67. {python_multipart-0.0.16 → python_multipart-0.0.18}/tests/test_data/http/utf8_filename.yaml +0 -0
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.18 (2024-11-28)
4
+
5
+ * Hard break if found data after last boundary on `MultipartParser` [#189](https://github.com/Kludex/python-multipart/pull/189).
6
+
7
+ ## 0.0.17 (2024-10-31)
8
+
9
+ * Handle PermissionError in fallback code for old import name [#182](https://github.com/Kludex/python-multipart/pull/182).
10
+
3
11
  ## 0.0.16 (2024-10-27)
4
12
 
5
13
  * Add dunder attributes to `multipart` package [#177](https://github.com/Kludex/python-multipart/pull/177).
@@ -1,14 +1,13 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: python-multipart
3
- Version: 0.0.16
3
+ Version: 0.0.18
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-Expression: Apache-2.0
11
- License-File: LICENSE.txt
10
+ License: Apache-2.0
12
11
  Classifier: Development Status :: 5 - Production/Stable
13
12
  Classifier: Environment :: Web Environment
14
13
  Classifier: Intended Audience :: Developers
@@ -0,0 +1,24 @@
1
+ # This only works if using a file system, other loaders not implemented.
2
+
3
+ import importlib.util
4
+ import sys
5
+ import warnings
6
+ from pathlib import Path
7
+
8
+ for p in sys.path:
9
+ file_path = Path(p, "multipart.py")
10
+ try:
11
+ if file_path.is_file():
12
+ spec = importlib.util.spec_from_file_location("multipart", file_path)
13
+ assert spec is not None, f"{file_path} found but not loadable!"
14
+ module = importlib.util.module_from_spec(spec)
15
+ sys.modules["multipart"] = module
16
+ assert spec.loader is not None, f"{file_path} must be loadable!"
17
+ spec.loader.exec_module(module)
18
+ break
19
+ except PermissionError:
20
+ pass
21
+ else:
22
+ warnings.warn("Please use `import python_multipart` instead.", PendingDeprecationWarning, stacklevel=2)
23
+ from python_multipart import *
24
+ from python_multipart import __all__, __author__, __copyright__, __license__, __version__
@@ -44,7 +44,7 @@ dev-dependencies = [
44
44
  "PyYAML==6.0.1",
45
45
  "invoke==2.2.0",
46
46
  "pytest-timeout==2.3.1",
47
- "ruff==0.3.4",
47
+ "ruff==0.8.0",
48
48
  "mypy",
49
49
  "types-PyYAML",
50
50
  "atheris==2.3.0; python_version != '3.12'",
@@ -122,4 +122,4 @@ exclude_lines = [
122
122
  ]
123
123
 
124
124
  [tool.check-sdist]
125
- git-only = ["docs", "fuzz", "scripts", "mkdocs.yml", "uv.lock"]
125
+ git-only = ["docs", "fuzz", "scripts", "mkdocs.yml", "uv.lock", "SECURITY.md"]
@@ -2,7 +2,7 @@
2
2
  __author__ = "Andrew Dunham"
3
3
  __license__ = "Apache"
4
4
  __copyright__ = "Copyright (c) 2012-2013, Andrew Dunham"
5
- __version__ = "0.0.16"
5
+ __version__ = "0.0.18"
6
6
 
7
7
  from .multipart import (
8
8
  BaseParser,
@@ -1105,7 +1105,6 @@ class MultipartParser(BaseParser):
1105
1105
  # Skip leading newlines
1106
1106
  if c == CR or c == LF:
1107
1107
  i += 1
1108
- self.logger.debug("Skipping leading CR/LF at %d", i)
1109
1108
  continue
1110
1109
 
1111
1110
  # index is used as in index into our boundary. Set to 0.
@@ -1398,9 +1397,10 @@ class MultipartParser(BaseParser):
1398
1397
  i -= 1
1399
1398
 
1400
1399
  elif state == MultipartState.END:
1401
- # Do nothing and just consume a byte in the end state.
1402
- if c not in (CR, LF):
1403
- self.logger.warning("Consuming a byte '0x%x' in the end state", c) # pragma: no cover
1400
+ # Skip data after the last boundary.
1401
+ self.logger.warning("Skipping data after last boundary")
1402
+ i = length
1403
+ break
1404
1404
 
1405
1405
  else: # pragma: no cover (error case)
1406
1406
  # We got into a strange state somehow! Just stop processing.
@@ -825,7 +825,7 @@ class TestFormParser(unittest.TestCase):
825
825
  return
826
826
 
827
827
  # No error!
828
- self.assertEqual(processed, len(param["test"]))
828
+ self.assertEqual(processed, len(param["test"]), param["name"])
829
829
 
830
830
  # Assert that the parser gave us the appropriate fields/files.
831
831
  for e in param["result"]["expected"]:
@@ -1210,6 +1210,44 @@ class TestFormParser(unittest.TestCase):
1210
1210
  self.assertEqual(fields[2].field_name, b"baz")
1211
1211
  self.assertEqual(fields[2].value, b"asdf")
1212
1212
 
1213
+ def test_multipart_parser_newlines_before_first_boundary(self) -> None:
1214
+ """This test makes sure that the parser does not handle when there is junk data after the last boundary."""
1215
+ num = 5_000_000
1216
+ data = (
1217
+ "\r\n" * num + "--boundary\r\n"
1218
+ 'Content-Disposition: form-data; name="file"; filename="filename.txt"\r\n'
1219
+ "Content-Type: text/plain\r\n\r\n"
1220
+ "hello\r\n"
1221
+ "--boundary--"
1222
+ )
1223
+
1224
+ files: list[File] = []
1225
+
1226
+ def on_file(f: FileProtocol) -> None:
1227
+ files.append(cast(File, f))
1228
+
1229
+ f = FormParser("multipart/form-data", on_field=Mock(), on_file=on_file, boundary="boundary")
1230
+ f.write(data.encode("latin-1"))
1231
+
1232
+ def test_multipart_parser_data_after_last_boundary(self) -> None:
1233
+ """This test makes sure that the parser does not handle when there is junk data after the last boundary."""
1234
+ num = 50_000_000
1235
+ data = (
1236
+ "--boundary\r\n"
1237
+ 'Content-Disposition: form-data; name="file"; filename="filename.txt"\r\n'
1238
+ "Content-Type: text/plain\r\n\r\n"
1239
+ "hello\r\n"
1240
+ "--boundary--" + "-" * num + "\r\n"
1241
+ )
1242
+
1243
+ files: list[File] = []
1244
+
1245
+ def on_file(f: FileProtocol) -> None:
1246
+ files.append(cast(File, f))
1247
+
1248
+ f = FormParser("multipart/form-data", on_field=Mock(), on_file=on_file, boundary="boundary")
1249
+ f.write(data.encode("latin-1"))
1250
+
1213
1251
  def test_max_size_multipart(self) -> None:
1214
1252
  # Load test data.
1215
1253
  test_file = "single_field_single_file.http"
@@ -1,21 +0,0 @@
1
- # This only works if using a file system, other loaders not implemented.
2
-
3
- import importlib.util
4
- import sys
5
- import warnings
6
- from pathlib import Path
7
-
8
- for p in sys.path:
9
- file_path = Path(p, "multipart.py")
10
- if file_path.is_file():
11
- spec = importlib.util.spec_from_file_location("multipart", file_path)
12
- assert spec is not None, f"{file_path} found but not loadable!"
13
- module = importlib.util.module_from_spec(spec)
14
- sys.modules["multipart"] = module
15
- assert spec.loader is not None, f"{file_path} must be loadable!"
16
- spec.loader.exec_module(module)
17
- break
18
- else:
19
- warnings.warn("Please use `import python_multipart` instead.", PendingDeprecationWarning, stacklevel=2)
20
- from python_multipart import *
21
- from python_multipart import __all__, __author__, __copyright__, __license__, __version__