aio-libs/aiohttp · error · ValueError

bad content disposition parameter

Error message

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

What it means

content_disposition_header raises ValueError when a parameter key in the params dict is empty or contains non-token characters. Parameter names in Content-Disposition headers must be valid RFC 2045 tokens (alphanumeric and certain special characters, no spaces or separators).

Solutions

  1. Sanitize parameter keys: remove or replace non-token characters
  2. Ensure keys are non-empty and contain only token characters
  3. Use standard parameter names: 'name', 'filename', 'size'

Example fix

# before
header = content_disposition_header('attachment', params={'': 'file.txt'})
# ValueError: bad content disposition parameter

# after
header = content_disposition_header('attachment', params={'filename': 'file.txt'})
Defensive patterns

Strategy: validation

Validate before calling

import re
TOKEN_RE = re.compile(r"^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$")

def sanitize_disposition_params(params):
    clean = {}
    for k, v in params.items():
        if k and TOKEN_RE.match(k):
            clean[k] = v
        else:
            logger.warning('Skipping invalid disposition param key: %r', k)
    return clean

Type guard

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

Try / catch

try:
    header = content_disposition_header(disptype, params=params)
except ValueError as e:
    if 'bad content disposition parameter' in str(e).lower():
        params = {k: v for k, v in params.items() if is_valid_param_key(k)}
        header = content_disposition_header(disptype, params=params)
    raise

Prevention

When it happens

Trigger: Calling content_disposition_header with a params dict that has an empty string key, or a key containing invalid characters like spaces, semicolons, or equals signs. Also triggered when set_content_disposition is called with keyword arguments that produce invalid parameter names.

Common situations: Building Content-Disposition params from user input or dynamic data without sanitizing keys; using 'filename*' or 'creation-date' which may contain hyphens (hyphens are actually valid tokens); passing a dict with an empty key from a filter that removed all characters.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/aad668458c493afd. Report an issue: GitHub.

Appendix: 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 d041d4d0fd)