python-multipart 0.0.19__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.
Files changed (70) hide show
  1. {python_multipart-0.0.19 → python_multipart-0.0.20}/CHANGELOG.md +4 -0
  2. {python_multipart-0.0.19 → python_multipart-0.0.20}/PKG-INFO +4 -3
  3. {python_multipart-0.0.19 → python_multipart-0.0.20}/pyproject.toml +1 -1
  4. {python_multipart-0.0.19 → python_multipart-0.0.20}/python_multipart/__init__.py +1 -1
  5. {python_multipart-0.0.19 → python_multipart-0.0.20}/python_multipart/multipart.py +20 -4
  6. python_multipart-0.0.20/tests/test_data/http/empty_message.http +1 -0
  7. python_multipart-0.0.20/tests/test_data/http/empty_message.yaml +2 -0
  8. python_multipart-0.0.20/tests/test_data/http/empty_message_with_bad_end.http +1 -0
  9. python_multipart-0.0.20/tests/test_data/http/empty_message_with_bad_end.yaml +3 -0
  10. {python_multipart-0.0.19 → python_multipart-0.0.20}/.gitignore +0 -0
  11. {python_multipart-0.0.19 → python_multipart-0.0.20}/LICENSE.txt +0 -0
  12. {python_multipart-0.0.19 → python_multipart-0.0.20}/README.md +0 -0
  13. {python_multipart-0.0.19 → python_multipart-0.0.20}/multipart/__init__.py +0 -0
  14. {python_multipart-0.0.19 → python_multipart-0.0.20}/multipart/decoders.py +0 -0
  15. {python_multipart-0.0.19 → python_multipart-0.0.20}/multipart/exceptions.py +0 -0
  16. {python_multipart-0.0.19 → python_multipart-0.0.20}/multipart/multipart.py +0 -0
  17. {python_multipart-0.0.19 → python_multipart-0.0.20}/python_multipart/decoders.py +0 -0
  18. {python_multipart-0.0.19 → python_multipart-0.0.20}/python_multipart/exceptions.py +0 -0
  19. {python_multipart-0.0.19 → python_multipart-0.0.20}/python_multipart/py.typed +0 -0
  20. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/__init__.py +0 -0
  21. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/compat.py +0 -0
  22. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/CRLF_in_header.http +0 -0
  23. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/CRLF_in_header.yaml +0 -0
  24. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/CR_in_header.http +0 -0
  25. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/CR_in_header.yaml +0 -0
  26. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/CR_in_header_value.http +0 -0
  27. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/CR_in_header_value.yaml +0 -0
  28. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary.http +0 -0
  29. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary.yaml +0 -0
  30. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary_without_CR.http +0 -0
  31. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary_without_CR.yaml +0 -0
  32. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary_without_LF.http +0 -0
  33. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary_without_LF.yaml +0 -0
  34. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary_without_final_hyphen.http +0 -0
  35. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/almost_match_boundary_without_final_hyphen.yaml +0 -0
  36. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/bad_end_of_headers.http +0 -0
  37. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/bad_end_of_headers.yaml +0 -0
  38. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/bad_header_char.http +0 -0
  39. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/bad_header_char.yaml +0 -0
  40. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/bad_initial_boundary.http +0 -0
  41. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/bad_initial_boundary.yaml +0 -0
  42. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/base64_encoding.http +0 -0
  43. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/base64_encoding.yaml +0 -0
  44. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/empty_header.http +0 -0
  45. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/empty_header.yaml +0 -0
  46. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/header_with_number.http +0 -0
  47. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/header_with_number.yaml +0 -0
  48. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/mixed_plain_and_base64_encoding.http +0 -0
  49. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/mixed_plain_and_base64_encoding.yaml +0 -0
  50. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/multiple_fields.http +0 -0
  51. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/multiple_fields.yaml +0 -0
  52. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/multiple_files.http +0 -0
  53. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/multiple_files.yaml +0 -0
  54. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/quoted_printable_encoding.http +0 -0
  55. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/quoted_printable_encoding.yaml +0 -0
  56. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/single_field.http +0 -0
  57. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/single_field.yaml +0 -0
  58. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/single_field_blocks.http +0 -0
  59. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/single_field_blocks.yaml +0 -0
  60. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/single_field_longer.http +0 -0
  61. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/single_field_longer.yaml +0 -0
  62. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/single_field_single_file.http +0 -0
  63. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/single_field_single_file.yaml +0 -0
  64. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/single_field_with_leading_newlines.http +0 -0
  65. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/single_field_with_leading_newlines.yaml +0 -0
  66. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/single_file.http +0 -0
  67. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/single_file.yaml +0 -0
  68. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/utf8_filename.http +0 -0
  69. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_data/http/utf8_filename.yaml +0 -0
  70. {python_multipart-0.0.19 → python_multipart-0.0.20}/tests/test_multipart.py +0 -0
@@ -1,5 +1,9 @@
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
+
3
7
  ## 0.0.19 (2024-11-30)
4
8
 
5
9
  * Don't warn when CRLF is found after last boundary on `MultipartParser` [#193](https://github.com/Kludex/python-multipart/pull/193).
@@ -1,13 +1,14 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: python-multipart
3
- Version: 0.0.19
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
@@ -47,7 +47,7 @@ dev-dependencies = [
47
47
  "ruff==0.8.0",
48
48
  "mypy",
49
49
  "types-PyYAML",
50
- "atheris==2.3.0; python_version != '3.12'",
50
+ "atheris==2.3.0; python_version <= '3.11'",
51
51
  # Documentation
52
52
  "mkdocs",
53
53
  "mkdocs-material",
@@ -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.19"
5
+ __version__ = "0.0.20"
6
6
 
7
7
  from .multipart import (
8
8
  BaseParser,
@@ -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
- END = 11
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 != CR:
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,6 +1400,18 @@ 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:
1400
1416
  # Don't do anything if chunk ends with CRLF.
1401
1417
  if c == CR and i + 1 < length and data[i + 1] == LF:
@@ -1707,8 +1723,8 @@ class FormParser:
1707
1723
 
1708
1724
  def _on_end() -> None:
1709
1725
  nonlocal writer
1710
- assert writer is not None
1711
- writer.finalize()
1726
+ if writer is not None:
1727
+ writer.finalize()
1712
1728
  if self.on_end is not None:
1713
1729
  self.on_end()
1714
1730
 
@@ -0,0 +1 @@
1
+ ----boundary--
@@ -0,0 +1,2 @@
1
+ boundary: --boundary
2
+ expected: []
@@ -0,0 +1,3 @@
1
+ boundary: --boundary
2
+ expected:
3
+ error: 13