Merge pull request 'Backport another hiperkitty bug.' (!25) from adellam/mailman:master into master

Reviewed-on: ISTI-ansible-roles/ansible-role-mailman#25
This commit is contained in:
Andrea Dell'Amico 2026-08-26 18:31:08 +02:00
commit b57c2cfa33
2 changed files with 60 additions and 7 deletions

View File

@ -60,6 +60,10 @@ legacy Python 3.6 web stack: malformed `From`/`Subject` headers, nested
gettext catalogs. It also normalizes non-ASCII sender addresses and backports
the byte-oriented delivery used by newer `mailman-hyperkitty` releases, so
surrogate-escaped message bytes do not pass through Requests as Unicode text.
Named MIME parts whose content type is missing or empty are extracted as binary
attachments instead of being appended to the plain-text message body. As an
additional safeguard, the NUL filtering introduced by django-mailman3 1.3.6 is
backported for scrubbed text fields only; binary attachments remain unchanged.
Enable it only for the known package versions:
```yaml

View File

@ -113,6 +113,42 @@ SCRUB_NEW = ''' if ctype == 'message/rfc822':
decodedpayload = str(payload)
'''
SCRUB_NAMED_TEXT_ATTACHMENT_OLD = ''' if ctype == 'text/plain':
if part.is_attachment():
attachments.append(self._parse_attachment(part, part_num))
part.set_content('\\n')
'''
SCRUB_NAMED_TEXT_ATTACHMENT_NEW = ''' if ctype == 'text/plain':
filename = part.get_filename()
if part.is_attachment() or filename:
# A missing or empty MIME type defaults to text/plain. If
# a filename is present, keep binary data out of the body.
raw_content_type = part.get('Content-Type', '')
declared_type = str(raw_content_type).split(';', 1)[0].strip()
if filename and not declared_type:
part.set_type('application/octet-stream')
attachments.append(self._parse_attachment(part, part_num))
part.set_content('\\n')
'''
SCRUB_NUL_ONE_PART_OLD = ''' if next_part_match:
result = result[0:next_part_match.start(0)]
return result
'''
SCRUB_NUL_ONE_PART_NEW = ''' if next_part_match:
result = result[0:next_part_match.start(0)]
# Backport from django-mailman3 1.3.6: PostgreSQL text fields cannot
# contain NUL characters.
return re.sub('\\x00', '', result)
'''
SCRUB_NUL_MULTIPART_OLD = ''' return '\\n'.join(text)
'''
SCRUB_NUL_MULTIPART_NEW = ''' # Backport from django-mailman3 1.3.6: PostgreSQL text fields
# cannot contain NUL characters.
return re.sub('\\x00', '', '\\n'.join(text))
'''
MAILMAN_HYPERKITTY_SEND_OLD = ''' message_text = msg.as_string()
except (MessageError, KeyError) as error:
'''
@ -185,13 +221,26 @@ def patch_hyperkitty(source):
def patch_scrubber(source):
if SCRUB_NEW in source:
if SCRUB_OLD in source:
raise RuntimeError('django-mailman3 compatibility patch is ambiguous')
return source, False
return replace_once(
source, SCRUB_OLD, SCRUB_NEW,
'django-mailman3 message/rfc822 handling'), True
updated = source
changed = False
replacements = (
(SCRUB_OLD, SCRUB_NEW, 'django-mailman3 message/rfc822 handling'),
(SCRUB_NAMED_TEXT_ATTACHMENT_OLD, SCRUB_NAMED_TEXT_ATTACHMENT_NEW,
'django-mailman3 named text attachment handling'),
(SCRUB_NUL_ONE_PART_OLD, SCRUB_NUL_ONE_PART_NEW,
'django-mailman3 single-part NUL handling'),
(SCRUB_NUL_MULTIPART_OLD, SCRUB_NUL_MULTIPART_NEW,
'django-mailman3 multipart NUL handling'),
)
for old, new, description in replacements:
if new in updated:
if old in updated:
raise RuntimeError(
'{} compatibility patch is ambiguous'.format(description))
continue
updated = replace_once(updated, old, new, description)
changed = True
return updated, changed
def patch_mailman_hyperkitty(source):