aio-libs/aiohttp · error · ValueError

bad content disposition parameter {key!r}={val!r}

Error message

bad content disposition parameter {key!r}={val!r}

What it means

Raised by content_disposition_header when a parameter key is empty or contains non-TOKEN characters. Each key (name, filename, etc.) must itself be an RFC 9110 token; values may be quoted/escaped but keys may not.

Source

Thrown at aiohttp/helpers.py:447

    Should be valid extension token (see RFC 2183)

    quote_fields performs value quoting to 7-bit MIME headers
    according to RFC 7578. Set to quote_fields to False if recipient
    can take 8-bit file names and field values.

    _charset specifies the charset to use when quote_fields is True.

    params is a dict with disposition params.
    """
    if not disptype or not (TOKEN > set(disptype)):
        raise ValueError(f"bad content disposition type {disptype!r}")

    value = disptype
    if params:
        lparams = []
        for key, val in params.items():
            if not key or not (TOKEN > set(key)):
                raise ValueError(f"bad content disposition parameter {key!r}={val!r}")
            if quote_fields:
                if key.lower() == "filename":
                    qval = quote(val, "", encoding=_charset)
                    lparams.append((key, '"%s"' % qval))
                else:
                    try:
                        qval = quoted_string(val)
                    except ValueError:
                        qval = "".join(
                            (_charset, "''", quote(val, "", encoding=_charset))
                        )
                        lparams.append((key + "*", qval))
                    else:
                        lparams.append((key, '"%s"' % qval))
            else:
                qval = val.replace("\\", "\\\\").replace('"', '\\"')
                lparams.append((key, '"%s"' % qval))
        sparams = "; ".join("=".join(pair) for pair in lparams)

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Use simple token keys: 'name', 'filename', 'size'.
  2. Sanitize keys: keep only TOKEN characters before calling.
  3. Validate each key with a TOKEN regex before building params.

Example fix

// before
content_disposition_header('attachment', params={'file name': 'x'})
// after
content_disposition_header('attachment', params={'filename': 'x'})
Defensive patterns

Strategy: validation

Validate before calling

import re
TOKEN_RE = re.compile(r"^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$")
def safe_params(p):
    for k in p:
        if not TOKEN_RE.fullmatch(k):
            raise ValueError(f'bad param key {k!r}')
    return p

Type guard

def is_valid_param_key(k) -> bool:
    import re
    return bool(re.fullmatch(r"[!#$%&'*+\-.^_`|~0-9A-Za-z]+", k))

Prevention

When it happens

Trigger: content_disposition_header('attachment', params={'': 'x'}) (empty key), params={'a b': 'v'} (space in key), params={'na"me': 'v'} (quote in key).

Common situations: Keys derived from user/DB data without sanitization; lowercase/uppercase normalization that introduces illegal chars; copying header snippets that include '=' in keys.

Related errors


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