QuotedPrintableDecoder: fix an escape split across two writes
What happens
QuotedPrintableDecoder.write() keeps the last two bytes for the next call when they contain =. A chunk that ends with a complete escape followed by a lone = (for example =41=) keeps 1= and decodes =4 as literal text:
from python_multipart.decoders import QuotedPrintableDecoder
class Sink:
def __init__(self): self.b = b""
def write(self, d): self.b += d; return len(d)
s = Sink(); d = QuotedPrintableDecoder(s)
d.write(b"=41="); d.write(b"42")
print(s.b) # b'=41B'; the same bytes in one write give b'AB'
The multipart parser uses this decoder for a part sent with Content-Transfer-Encoding: quoted-printable, and the chunks follow the caller's reads, so the decoded upload depends on where the reads fall.
The fix
Keep the bytes from the last = within the final two, not always the final two: data.rfind(b"=", max(0, len(data) - 2)). A complete escape is then decoded at once and only an unfinished one (=, =4) waits for the next write.
Tests
tests/test_quoted_printable_chunks.py added: every two-way split of four inputs must decode like the whole input. It fails on the current code (4 failures) and passes with the fix. Full suite: 163 passed before, 167 after.
š¤ Generated with Claude Code