Skip to content
Closed
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
5 changes: 3 additions & 2 deletions python_multipart/decoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,9 @@ def write(self, data: bytes) -> int:
# If the last 2 characters have an '=' sign in it, then we won't be
# able to decode the encoded value and we'll need to save it for the
# next decoding step.
if data[-2:].find(b"=") != -1:
enc, rest = data[:-2], data[-2:]
idx = data.rfind(b"=", max(0, len(data) - 2))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: rfind can split adjacent = bytes and decode them separately, changing accepted malformed input. For b"==", write() followed by finalize() now emits b"" instead of b"="; preserve the entire trailing run of = bytes (or reject it consistently).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At python_multipart/decoders.py, line 145:

<comment>`rfind` can split adjacent `=` bytes and decode them separately, changing accepted malformed input. For `b"=="`, `write()` followed by `finalize()` now emits `b""` instead of `b"="`; preserve the entire trailing run of `=` bytes (or reject it consistently).</comment>

<file context>
@@ -142,8 +142,9 @@ def write(self, data: bytes) -> int:
         # next decoding step.
-        if data[-2:].find(b"=") != -1:
-            enc, rest = data[:-2], data[-2:]
+        idx = data.rfind(b"=", max(0, len(data) - 2))
+        if idx != -1:
+            enc, rest = data[:idx], data[idx:]
</file context>
Suggested change
idx = data.rfind(b"=", max(0, len(data) - 2))
idx = data.rfind(b"=", max(0, len(data) - 2))
while idx > 0 and data[idx - 1 : idx] == b"=":
idx -= 1

if idx != -1:
enc, rest = data[:idx], data[idx:]
else:
enc = data
rest = b""
Expand Down
30 changes: 30 additions & 0 deletions tests/test_quoted_printable_chunks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import binascii

import pytest

from python_multipart.decoders import QuotedPrintableDecoder


class Sink:
def __init__(self) -> None:
self.b = b""

def write(self, data: bytes) -> int:
self.b += data
return len(data)


@pytest.mark.parametrize(
"data",
[b"=41=42", b"a=41=42=43z", b"=41=\n=42", b"x=3D=3Dy"],
)
def test_chunked_matches_whole(data: bytes) -> None:
whole = binascii.a2b_qp(data)
for i in range(len(data) + 1):
for j in range(i, len(data) + 1):
sink = Sink()
decoder = QuotedPrintableDecoder(sink)
for part in (data[:i], data[i:j], data[j:]):
decoder.write(part)
decoder.finalize()
assert sink.b == whole, (i, j)
Loading