aio-libs/aiohttp · error · ValueError

bad content for quoted-string {content!r}

Error message

bad content for quoted-string {content!r}

What it means

Raised by quoted_string() when the content contains characters outside QCONTENT (printable 7-bit US-ASCII 0x20-0x7E plus tab). The function formats MIME quoted-strings per RFC 5322, which is 7-bit only; any 8-bit/control char is rejected with ValueError before quoting.

Source

Thrown at aiohttp/helpers.py:412

    if name and isinstance(name, str) and name[0] != "<" and name[-1] != ">":
        return Path(name).name
    return default


not_qtext_re = re.compile(r"[^\041\043-\133\135-\176]")
QCONTENT = {chr(i) for i in range(0x20, 0x7F)} | {"\t"}


def quoted_string(content: str) -> str:
    """Return 7-bit content as quoted-string.

    Format content into a quoted-string as defined in RFC5322 for
    Internet Message Format. Notice that this is not the 8-bit HTTP
    format, but the 7-bit email format. Content must be in usascii or
    a ValueError is raised.
    """
    if not (QCONTENT > set(content)):
        raise ValueError(f"bad content for quoted-string {content!r}")
    return not_qtext_re.sub(lambda x: "\\" + x.group(0), content)


def content_disposition_header(
    disptype: str,
    quote_fields: bool = True,
    _charset: str = "utf-8",
    params: dict[str, str] | None = None,
) -> str:
    """Sets ``Content-Disposition`` header for MIME.

    This is the MIME payload Content-Disposition header from RFC 2183
    and RFC 7579 section 4.2, not the HTTP Content-Disposition from
    RFC 6266.

    disptype is a disposition type: inline, attachment, form-data.
    Should be valid extension token (see RFC 2183)

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Use content_disposition_header() (which gracefully falls back) instead of quoted_string() directly.
  2. Strip/normalize non-ASCII before calling quoted_string.
  3. Encode the value (e.g. percent-encoding) for 7-bit transport.

Example fix

// before
quoted_string('résumé.pdf')  # raises
// after
content_disposition_header('attachment', quote_fields=True, params={'filename': 'résumé.pdf'})  # auto-fallback to filename*
Defensive patterns

Strategy: validation

Validate before calling

QCONTENT = {chr(i) for i in range(0x20, 0x7F)} | {'\t'}
def is_quoted_string_safe(s) -> bool:
    return QCONTENT > set(s)

Type guard

def is_ascii_printable(s) -> bool:
    return all('\t' <= ch <= '~' for ch in s)

Prevention

When it happens

Trigger: content_disposition_header with quote_fields=True and a parameter value containing non-ASCII (Unicode filenames) — but the inner quoted_string() call is wrapped in try/except that falls back to RFC 5987 extended notation, so direct calls to quoted_string() are the real trigger. Calling helpers.quoted_string('café') directly.

Common situations: User-supplied Unicode filenames passed to quoted_string directly; control chars in metadata; emoji in field names.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/0ee2b986c89bea7a.json. Report an issue: GitHub.