FA-76381 / Email MIME structure / Open access
Resolve a part's effective content type and charset: digest default · case 01
Entries of a digest without their own Content-Type are shown as plain text instead of as messages.
ROOT CAUSE
The digest default decision evaluates `default = 'text/plain'` where the contract requires `default = 'message/rfc822' if parent_type == 'multipart/digest' else 'text/plain'`.
VERIFIED REPAIR
Use `default = 'message/rfc822' if parent_type == 'multipart/digest' else 'text/plain'` for the digest default decision and keep every other rule of the model unchanged.
Unsuccessful approach: Keying the default on a message parent applies the digest rule to the body of an encapsulated message. The attempted `default = 'message/rfc822' if parent_type.startswith('message/') else 'text/plain'` still disagrees with a fixture.
Case contract
header is the raw Content-Type value or None. The type is the lower-cased text before ";" with surrounding spaces removed; it is valid only with a non-empty major and minor separated by "/" and no inner space. Missing or invalid types default to message/rfc822 inside multipart/digest and text/plain everywhere else (parameters of an invalid header are ignored). text/* types get the charset parameter (quotes removed, lower-cased) or us-ascii; other types have charset None. Result [type, charset].
Why this case matters
Default and fallback types decide whether a part is rendered as text, as a nested message or as an attachment.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(parent_type, header):
default = 'text/plain'
mtype = default
charset = None
if header is not None:
head, _, rest = header.partition(';')
cand = head.strip().lower()
major, slash, minor = cand.partition('/')
if slash and major and minor and ' ' not in cand:
mtype = cand
for piece in rest.split(';'):
k, eq, v = piece.partition('=')
if eq and k.strip().lower() == 'charset':
charset = v.strip().strip('"').lower()
if mtype.startswith('text/'):
charset = charset or 'us-ascii'
else:
charset = None
return [mtype, charset]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
_CASES = {1: [('digest child without header', ('multipart/digest', None), ['message/rfc822', None]), ('mixed child without header', ('multipart/mixed', None), ['text/plain', 'us-ascii']), ('truncated type in digest', ('multipart/digest', 'text/'), ['message/rfc822', None]), ('missing major type', ('multipart/mixed', '/html; charset=utf-8'), ['text/plain', 'us-ascii']), ('quoted upper-case charset', ('multipart/mixed', 'Text/HTML; charset="UTF-8"'), ['text/html', 'utf-8']), ('charset on a binary type', ('multipart/mixed', 'image/png; charset=binary'), ['image/png', None]), ('padded header', ('multipart/alternative', ' text/plain ; charset=ISO-8859-1'), ['text/plain', 'iso-8859-1']), ('nested message parent', ('message/rfc822', None), ['text/plain', 'us-ascii'])], 2: [('digest child without header', ('multipart/digest', None), ['message/rfc822', None]), ('mixed child without header', ('multipart/mixed', None), ['text/plain', 'us-ascii']), ('truncated type in digest', ('multipart/digest', 'text/'), ['message/rfc822', None]), ('missing major type', ('multipart/mixed', '/html; charset=utf-8'), ['text/plain', 'us-ascii']), ('quoted upper-case charset', ('multipart/mixed', 'Text/HTML; charset="Windows-1252"'), ['text/html', 'windows-1252']), ('charset on a binary type', ('multipart/mixed', 'image/png; charset=binary'), ['image/png', None]), ('padded header', ('multipart/alternative', ' text/plain ; charset=ISO-8859-2'), ['text/plain', 'iso-8859-2']), ('nested message parent', ('message/rfc822', None), ['text/plain', 'us-ascii'])], 3: [('digest child without header', ('multipart/digest', None), ['message/rfc822', None]), ('mixed child without header', ('multipart/mixed', None), ['text/plain', 'us-ascii']), ('truncated type in digest', ('multipart/digest', 'text/'), ['message/rfc822', None]), ('missing major type', ('multipart/mixed', '/html; charset=utf-8'), ['text/plain', 'us-ascii']), ('quoted upper-case charset', ('multipart/mixed', 'Text/HTML; charset="KOI8-R"'), ['text/html', 'koi8-r']), ('charset on a binary type', ('multipart/mixed', 'image/png; charset=binary'), ['image/png', None]), ('padded header', ('multipart/alternative', ' text/plain ; charset=ISO-8859-3'), ['text/plain', 'iso-8859-3']), ('nested message parent', ('message/rfc822', None), ['text/plain', 'us-ascii'])], 4: [('digest child without header', ('multipart/digest', None), ['message/rfc822', None]), ('mixed child without header', ('multipart/mixed', None), ['text/plain', 'us-ascii']), ('truncated type in digest', ('multipart/digest', 'text/'), ['message/rfc822', None]), ('missing major type', ('multipart/mixed', '/html; charset=utf-8'), ['text/plain', 'us-ascii']), ('quoted upper-case charset', ('multipart/mixed', 'Text/HTML; charset="Shift_JIS"'), ['text/html', 'shift_jis']), ('charset on a binary type', ('multipart/mixed', 'image/png; charset=binary'), ['image/png', None]), ('padded header', ('multipart/alternative', ' text/plain ; charset=ISO-8859-4'), ['text/plain', 'iso-8859-4']), ('nested message parent', ('message/rfc822', None), ['text/plain', 'us-ascii'])], 5: [('digest child without header', ('multipart/digest', None), ['message/rfc822', None]), ('mixed child without header', ('multipart/mixed', None), ['text/plain', 'us-ascii']), ('truncated type in digest', ('multipart/digest', 'text/'), ['message/rfc822', None]), ('missing major type', ('multipart/mixed', '/html; charset=utf-8'), ['text/plain', 'us-ascii']), ('quoted upper-case charset', ('multipart/mixed', 'Text/HTML; charset="EUC-KR"'), ['text/html', 'euc-kr']), ('charset on a binary type', ('multipart/mixed', 'image/png; charset=binary'), ['image/png', None]), ('padded header', ('multipart/alternative', ' text/plain ; charset=ISO-8859-5'), ['text/plain', 'iso-8859-5']), ('nested message parent', ('message/rfc822', None), ['text/plain', 'us-ascii'])]}
for _label, _args, _expected in _CASES[N]:
check(_label, solve(*_args), _expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| digest child without header | ['text/plain', 'us-ascii'] | ['message/rfc822', None] | Failed |
| mixed child without header | ['text/plain', 'us-ascii'] | ['text/plain', 'us-ascii'] | Passed |
| truncated type in digest | ['text/plain', 'us-ascii'] | ['message/rfc822', None] | Failed |
| missing major type | ['text/plain', 'us-ascii'] | ['text/plain', 'us-ascii'] | Passed |
| quoted upper-case charset | ['text/html', 'utf-8'] | ['text/html', 'utf-8'] | Passed |
| charset on a binary type | ['image/png', None] | ['image/png', None] | Passed |
| padded header | ['text/plain', 'iso-8859-1'] | ['text/plain', 'iso-8859-1'] | Passed |
| nested message parent | ['text/plain', 'us-ascii'] | ['text/plain', 'us-ascii'] | Passed |
SHA-256 / 76d8770484b92ebfaa7f0dfbe0f749c577fc5c37460770b719810a2fd9ca733c
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(parent_type, header):
default = 'message/rfc822' if parent_type.startswith('message/') else 'text/plain'
mtype = default
charset = None
if header is not None:
head, _, rest = header.partition(';')
cand = head.strip().lower()
major, slash, minor = cand.partition('/')
if slash and major and minor and ' ' not in cand:
mtype = cand
for piece in rest.split(';'):
k, eq, v = piece.partition('=')
if eq and k.strip().lower() == 'charset':
charset = v.strip().strip('"').lower()
if mtype.startswith('text/'):
charset = charset or 'us-ascii'
else:
charset = None
return [mtype, charset]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
_CASES = {1: [('digest child without header', ('multipart/digest', None), ['message/rfc822', None]), ('mixed child without header', ('multipart/mixed', None), ['text/plain', 'us-ascii']), ('truncated type in digest', ('multipart/digest', 'text/'), ['message/rfc822', None]), ('missing major type', ('multipart/mixed', '/html; charset=utf-8'), ['text/plain', 'us-ascii']), ('quoted upper-case charset', ('multipart/mixed', 'Text/HTML; charset="UTF-8"'), ['text/html', 'utf-8']), ('charset on a binary type', ('multipart/mixed', 'image/png; charset=binary'), ['image/png', None]), ('padded header', ('multipart/alternative', ' text/plain ; charset=ISO-8859-1'), ['text/plain', 'iso-8859-1']), ('nested message parent', ('message/rfc822', None), ['text/plain', 'us-ascii'])], 2: [('digest child without header', ('multipart/digest', None), ['message/rfc822', None]), ('mixed child without header', ('multipart/mixed', None), ['text/plain', 'us-ascii']), ('truncated type in digest', ('multipart/digest', 'text/'), ['message/rfc822', None]), ('missing major type', ('multipart/mixed', '/html; charset=utf-8'), ['text/plain', 'us-ascii']), ('quoted upper-case charset', ('multipart/mixed', 'Text/HTML; charset="Windows-1252"'), ['text/html', 'windows-1252']), ('charset on a binary type', ('multipart/mixed', 'image/png; charset=binary'), ['image/png', None]), ('padded header', ('multipart/alternative', ' text/plain ; charset=ISO-8859-2'), ['text/plain', 'iso-8859-2']), ('nested message parent', ('message/rfc822', None), ['text/plain', 'us-ascii'])], 3: [('digest child without header', ('multipart/digest', None), ['message/rfc822', None]), ('mixed child without header', ('multipart/mixed', None), ['text/plain', 'us-ascii']), ('truncated type in digest', ('multipart/digest', 'text/'), ['message/rfc822', None]), ('missing major type', ('multipart/mixed', '/html; charset=utf-8'), ['text/plain', 'us-ascii']), ('quoted upper-case charset', ('multipart/mixed', 'Text/HTML; charset="KOI8-R"'), ['text/html', 'koi8-r']), ('charset on a binary type', ('multipart/mixed', 'image/png; charset=binary'), ['image/png', None]), ('padded header', ('multipart/alternative', ' text/plain ; charset=ISO-8859-3'), ['text/plain', 'iso-8859-3']), ('nested message parent', ('message/rfc822', None), ['text/plain', 'us-ascii'])], 4: [('digest child without header', ('multipart/digest', None), ['message/rfc822', None]), ('mixed child without header', ('multipart/mixed', None), ['text/plain', 'us-ascii']), ('truncated type in digest', ('multipart/digest', 'text/'), ['message/rfc822', None]), ('missing major type', ('multipart/mixed', '/html; charset=utf-8'), ['text/plain', 'us-ascii']), ('quoted upper-case charset', ('multipart/mixed', 'Text/HTML; charset="Shift_JIS"'), ['text/html', 'shift_jis']), ('charset on a binary type', ('multipart/mixed', 'image/png; charset=binary'), ['image/png', None]), ('padded header', ('multipart/alternative', ' text/plain ; charset=ISO-8859-4'), ['text/plain', 'iso-8859-4']), ('nested message parent', ('message/rfc822', None), ['text/plain', 'us-ascii'])], 5: [('digest child without header', ('multipart/digest', None), ['message/rfc822', None]), ('mixed child without header', ('multipart/mixed', None), ['text/plain', 'us-ascii']), ('truncated type in digest', ('multipart/digest', 'text/'), ['message/rfc822', None]), ('missing major type', ('multipart/mixed', '/html; charset=utf-8'), ['text/plain', 'us-ascii']), ('quoted upper-case charset', ('multipart/mixed', 'Text/HTML; charset="EUC-KR"'), ['text/html', 'euc-kr']), ('charset on a binary type', ('multipart/mixed', 'image/png; charset=binary'), ['image/png', None]), ('padded header', ('multipart/alternative', ' text/plain ; charset=ISO-8859-5'), ['text/plain', 'iso-8859-5']), ('nested message parent', ('message/rfc822', None), ['text/plain', 'us-ascii'])]}
for _label, _args, _expected in _CASES[N]:
check(_label, solve(*_args), _expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| digest child without header | ['text/plain', 'us-ascii'] | ['message/rfc822', None] | Failed |
| mixed child without header | ['text/plain', 'us-ascii'] | ['text/plain', 'us-ascii'] | Passed |
| truncated type in digest | ['text/plain', 'us-ascii'] | ['message/rfc822', None] | Failed |
| missing major type | ['text/plain', 'us-ascii'] | ['text/plain', 'us-ascii'] | Passed |
| quoted upper-case charset | ['text/html', 'utf-8'] | ['text/html', 'utf-8'] | Passed |
| charset on a binary type | ['image/png', None] | ['image/png', None] | Passed |
| padded header | ['text/plain', 'iso-8859-1'] | ['text/plain', 'iso-8859-1'] | Passed |
| nested message parent | ['message/rfc822', None] | ['text/plain', 'us-ascii'] | Failed |
SHA-256 / 316cf47d3371a10a4c1573b636920302cf8057628b1e16ce1f2f60450d605cfa
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(parent_type, header):
default = 'message/rfc822' if parent_type == 'multipart/digest' else 'text/plain'
mtype = default
charset = None
if header is not None:
head, _, rest = header.partition(';')
cand = head.strip().lower()
major, slash, minor = cand.partition('/')
if slash and major and minor and ' ' not in cand:
mtype = cand
for piece in rest.split(';'):
k, eq, v = piece.partition('=')
if eq and k.strip().lower() == 'charset':
charset = v.strip().strip('"').lower()
if mtype.startswith('text/'):
charset = charset or 'us-ascii'
else:
charset = None
return [mtype, charset]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
_CASES = {1: [('digest child without header', ('multipart/digest', None), ['message/rfc822', None]), ('mixed child without header', ('multipart/mixed', None), ['text/plain', 'us-ascii']), ('truncated type in digest', ('multipart/digest', 'text/'), ['message/rfc822', None]), ('missing major type', ('multipart/mixed', '/html; charset=utf-8'), ['text/plain', 'us-ascii']), ('quoted upper-case charset', ('multipart/mixed', 'Text/HTML; charset="UTF-8"'), ['text/html', 'utf-8']), ('charset on a binary type', ('multipart/mixed', 'image/png; charset=binary'), ['image/png', None]), ('padded header', ('multipart/alternative', ' text/plain ; charset=ISO-8859-1'), ['text/plain', 'iso-8859-1']), ('nested message parent', ('message/rfc822', None), ['text/plain', 'us-ascii'])], 2: [('digest child without header', ('multipart/digest', None), ['message/rfc822', None]), ('mixed child without header', ('multipart/mixed', None), ['text/plain', 'us-ascii']), ('truncated type in digest', ('multipart/digest', 'text/'), ['message/rfc822', None]), ('missing major type', ('multipart/mixed', '/html; charset=utf-8'), ['text/plain', 'us-ascii']), ('quoted upper-case charset', ('multipart/mixed', 'Text/HTML; charset="Windows-1252"'), ['text/html', 'windows-1252']), ('charset on a binary type', ('multipart/mixed', 'image/png; charset=binary'), ['image/png', None]), ('padded header', ('multipart/alternative', ' text/plain ; charset=ISO-8859-2'), ['text/plain', 'iso-8859-2']), ('nested message parent', ('message/rfc822', None), ['text/plain', 'us-ascii'])], 3: [('digest child without header', ('multipart/digest', None), ['message/rfc822', None]), ('mixed child without header', ('multipart/mixed', None), ['text/plain', 'us-ascii']), ('truncated type in digest', ('multipart/digest', 'text/'), ['message/rfc822', None]), ('missing major type', ('multipart/mixed', '/html; charset=utf-8'), ['text/plain', 'us-ascii']), ('quoted upper-case charset', ('multipart/mixed', 'Text/HTML; charset="KOI8-R"'), ['text/html', 'koi8-r']), ('charset on a binary type', ('multipart/mixed', 'image/png; charset=binary'), ['image/png', None]), ('padded header', ('multipart/alternative', ' text/plain ; charset=ISO-8859-3'), ['text/plain', 'iso-8859-3']), ('nested message parent', ('message/rfc822', None), ['text/plain', 'us-ascii'])], 4: [('digest child without header', ('multipart/digest', None), ['message/rfc822', None]), ('mixed child without header', ('multipart/mixed', None), ['text/plain', 'us-ascii']), ('truncated type in digest', ('multipart/digest', 'text/'), ['message/rfc822', None]), ('missing major type', ('multipart/mixed', '/html; charset=utf-8'), ['text/plain', 'us-ascii']), ('quoted upper-case charset', ('multipart/mixed', 'Text/HTML; charset="Shift_JIS"'), ['text/html', 'shift_jis']), ('charset on a binary type', ('multipart/mixed', 'image/png; charset=binary'), ['image/png', None]), ('padded header', ('multipart/alternative', ' text/plain ; charset=ISO-8859-4'), ['text/plain', 'iso-8859-4']), ('nested message parent', ('message/rfc822', None), ['text/plain', 'us-ascii'])], 5: [('digest child without header', ('multipart/digest', None), ['message/rfc822', None]), ('mixed child without header', ('multipart/mixed', None), ['text/plain', 'us-ascii']), ('truncated type in digest', ('multipart/digest', 'text/'), ['message/rfc822', None]), ('missing major type', ('multipart/mixed', '/html; charset=utf-8'), ['text/plain', 'us-ascii']), ('quoted upper-case charset', ('multipart/mixed', 'Text/HTML; charset="EUC-KR"'), ['text/html', 'euc-kr']), ('charset on a binary type', ('multipart/mixed', 'image/png; charset=binary'), ['image/png', None]), ('padded header', ('multipart/alternative', ' text/plain ; charset=ISO-8859-5'), ['text/plain', 'iso-8859-5']), ('nested message parent', ('message/rfc822', None), ['text/plain', 'us-ascii'])]}
for _label, _args, _expected in _CASES[N]:
check(_label, solve(*_args), _expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| digest child without header | ['message/rfc822', None] | ['message/rfc822', None] | Passed |
| mixed child without header | ['text/plain', 'us-ascii'] | ['text/plain', 'us-ascii'] | Passed |
| truncated type in digest | ['message/rfc822', None] | ['message/rfc822', None] | Passed |
| missing major type | ['text/plain', 'us-ascii'] | ['text/plain', 'us-ascii'] | Passed |
| quoted upper-case charset | ['text/html', 'utf-8'] | ['text/html', 'utf-8'] | Passed |
| charset on a binary type | ['image/png', None] | ['image/png', None] | Passed |
| padded header | ['text/plain', 'iso-8859-1'] | ['text/plain', 'iso-8859-1'] | Passed |
| nested message parent | ['text/plain', 'us-ascii'] | ['text/plain', 'us-ascii'] | Passed |
SHA-256 / 937ad6c7c2b1dfe1d377beba22461086b4ba8ea288085303e37e9d34693b027e
Verification & scope
Stipulated offline model over pre-parsed MIME dictionaries; not a conforming MIME parser, generator or mail client. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:49:15.830521+00:00.
Case digest / ddd9e3d3cf4ff48fc0378292851eca3c04c1398e98e0eb3aaf0440d9d3718a9c