Skip to content

Commit 8927a33

Browse files
committed
Fix body line iteration to properly decode Content-Transfer-Encoding
The previous fix (removing decode=True) stopped the crash but left quoted-printable and base64 bodies undecoded, causing e.g. QP soft line breaks (=20) to appear literally in addresses and break pattern matching. Add _body_line_iterator() to SimpleMatch which uses get_payload(decode=True) to get CTE-decoded bytes, then decodes to str using the part's charset. SimpleWarning imports and uses the same helper. Tagger likewise imports it so topic matching works correctly on encoded message bodies.
1 parent 823eba6 commit 8927a33

3 files changed

Lines changed: 25 additions & 4 deletions

File tree

Mailman/Bouncers/SimpleMatch.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,30 @@
1717

1818
"""Recognizes simple heuristically delimited bounces."""
1919

20+
import io
2021
import re
2122
import email.iterators
2223

2324

25+
def _body_line_iterator(msg):
26+
"""Iterate body lines with Content-Transfer-Encoding properly decoded.
27+
28+
Python 3's body_line_iterator only yields str payloads; get_payload(
29+
decode=True) returns bytes (CTE-decoded). This helper bridges the gap so
30+
quoted-printable and base64 bodies are decoded before pattern matching.
31+
"""
32+
for subpart in email.iterators.typed_subpart_iterator(msg):
33+
payload = subpart.get_payload(decode=True)
34+
if not isinstance(payload, bytes):
35+
continue
36+
charset = subpart.get_content_charset('us-ascii') or 'us-ascii'
37+
try:
38+
text = payload.decode(charset, errors='replace')
39+
except LookupError:
40+
text = payload.decode('us-ascii', errors='replace')
41+
yield from io.StringIO(text)
42+
43+
2444

2545
def _c(pattern):
2646
return re.compile(pattern, re.IGNORECASE)
@@ -224,7 +244,7 @@ def process(msg, patterns=None):
224244
# we process the message multiple times anyway.
225245
for scre, ecre, acre in patterns:
226246
state = 0
227-
for line in email.iterators.body_line_iterator(msg):
247+
for line in _body_line_iterator(msg):
228248
if state == 0:
229249
if scre.search(line):
230250
state = 1

Mailman/Bouncers/SimpleWarning.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import email.iterators
2222

2323
from Mailman.Bouncers.BouncerAPI import Stop
24-
from Mailman.Bouncers.SimpleMatch import _c
24+
from Mailman.Bouncers.SimpleMatch import _c, _body_line_iterator
2525

2626

2727

@@ -75,7 +75,7 @@ def process(msg):
7575
addrs = {}
7676
for scre, ecre, acre in patterns:
7777
state = 0
78-
for line in email.iterators.body_line_iterator(msg):
78+
for line in _body_line_iterator(msg):
7979
if state == 0:
8080
if scre.search(line):
8181
state = 1

Mailman/Handlers/Tagger.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from email.header import decode_header
2727

2828
from Mailman import Utils
29+
from Mailman.Bouncers.SimpleMatch import _body_line_iterator
2930
from Mailman.Logging.Syslog import syslog
3031
from Mailman.Handlers.CookHeaders import change_header
3132

@@ -97,7 +98,7 @@ def scanbody(msg, numlines=None):
9798
# the first numlines of body text.
9899
lines = []
99100
lineno = 0
100-
reader = list(email.iterators.body_line_iterator(msg))
101+
reader = list(_body_line_iterator(msg))
101102
while numlines is None or lineno < numlines:
102103
try:
103104
line = reader.pop(0)

0 commit comments

Comments
 (0)