Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.0.19 (2024-11-30)

* Don't warn when CRLF is found after last boundary on `MultipartParser` [#193](https://github.com/Kludex/python-multipart/pull/193).

## 0.0.18 (2024-11-28)

* Hard break if found data after last boundary on `MultipartParser` [#189](https://github.com/Kludex/python-multipart/pull/189).
Expand Down
2 changes: 1 addition & 1 deletion python_multipart/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
__author__ = "Andrew Dunham"
__license__ = "Apache"
__copyright__ = "Copyright (c) 2012-2013, Andrew Dunham"
__version__ = "0.0.18"
__version__ = "0.0.19"

from .multipart import (
BaseParser,
Expand Down
4 changes: 4 additions & 0 deletions python_multipart/multipart.py
Original file line number Diff line number Diff line change
Expand Up @@ -1397,6 +1397,10 @@ def data_callback(name: CallbackName, end_i: int, remaining: bool = False) -> No
i -= 1

elif state == MultipartState.END:
# Don't do anything if chunk ends with CRLF.
if c == CR and i + 1 < length and data[i + 1] == LF:
i += 2
continue
# Skip data after the last boundary.
self.logger.warning("Skipping data after last boundary")
i = length
Expand Down
2 changes: 1 addition & 1 deletion scripts/check
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ SOURCE_FILES="python_multipart multipart tests"

uvx ruff format --check --diff $SOURCE_FILES
uvx ruff check $SOURCE_FILES
uvx --with types-PyYAML mypy $SOURCE_FILES
uv run mypy $SOURCE_FILES
uvx check-sdist
26 changes: 26 additions & 0 deletions tests/test_multipart.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import logging
import os
import random
import sys
Expand All @@ -9,6 +10,7 @@
from typing import TYPE_CHECKING, cast
from unittest.mock import Mock

import pytest
import yaml

from python_multipart.decoders import Base64Decoder, QuotedPrintableDecoder
Expand Down Expand Up @@ -1248,6 +1250,30 @@ def on_file(f: FileProtocol) -> None:
f = FormParser("multipart/form-data", on_field=Mock(), on_file=on_file, boundary="boundary")
f.write(data.encode("latin-1"))

@pytest.fixture(autouse=True)
def inject_fixtures(self, caplog: pytest.LogCaptureFixture) -> None:
self._caplog = caplog

def test_multipart_parser_data_end_with_crlf_without_warnings(self) -> None:
"""This test makes sure that the parser does not handle when the data ends with a CRLF."""
data = (
"--boundary\r\n"
'Content-Disposition: form-data; name="file"; filename="filename.txt"\r\n'
"Content-Type: text/plain\r\n\r\n"
"hello\r\n"
"--boundary--\r\n"
)

files: list[File] = []

def on_file(f: FileProtocol) -> None:
files.append(cast(File, f))

f = FormParser("multipart/form-data", on_field=Mock(), on_file=on_file, boundary="boundary")
with self._caplog.at_level(logging.WARNING):
f.write(data.encode("latin-1"))
assert len(self._caplog.records) == 0

def test_max_size_multipart(self) -> None:
# Load test data.
test_file = "single_field_single_file.http"
Expand Down