FAILURE MAP
← Case archive

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.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

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 fixtureActualExpectedOutcome
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 fixtureActualExpectedOutcome
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 fixtureActualExpectedOutcome
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