{"id":"aad668458c493afd","repo":"aio-libs/aiohttp","slug":"bad-content-disposition-parameter-key-r-val-r","errorCode":null,"errorMessage":"bad content disposition parameter {key!r}={val!r}","messagePattern":"bad content disposition parameter (.+?)=(.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/helpers.py","lineNumber":447,"sourceCode":"    Should be valid extension token (see RFC 2183)\n\n    quote_fields performs value quoting to 7-bit MIME headers\n    according to RFC 7578. Set to quote_fields to False if recipient\n    can take 8-bit file names and field values.\n\n    _charset specifies the charset to use when quote_fields is True.\n\n    params is a dict with disposition params.\n    \"\"\"\n    if not disptype or not (TOKEN > set(disptype)):\n        raise ValueError(f\"bad content disposition type {disptype!r}\")\n\n    value = disptype\n    if params:\n        lparams = []\n        for key, val in params.items():\n            if not key or not (TOKEN > set(key)):\n                raise ValueError(f\"bad content disposition parameter {key!r}={val!r}\")\n            if quote_fields:\n                if key.lower() == \"filename\":\n                    qval = quote(val, \"\", encoding=_charset)\n                    lparams.append((key, '\"%s\"' % qval))\n                else:\n                    try:\n                        qval = quoted_string(val)\n                    except ValueError:\n                        qval = \"\".join(\n                            (_charset, \"''\", quote(val, \"\", encoding=_charset))\n                        )\n                        lparams.append((key + \"*\", qval))\n                    else:\n                        lparams.append((key, '\"%s\"' % qval))\n            else:\n                qval = val.replace(\"\\\\\", \"\\\\\\\\\").replace('\"', '\\\\\"')\n                lparams.append((key, '\"%s\"' % qval))\n        sparams = \"; \".join(\"=\".join(pair) for pair in lparams)","sourceCodeStart":429,"sourceCodeEnd":465,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/helpers.py#L429-L465","documentation":"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.","triggerScenarios":"content_disposition_header('attachment', params={'': 'x'}) (empty key), params={'a b': 'v'} (space in key), params={'na\"me': 'v'} (quote in key).","commonSituations":"Keys derived from user/DB data without sanitization; lowercase/uppercase normalization that introduces illegal chars; copying header snippets that include '=' in keys.","solutions":["Use simple token keys: 'name', 'filename', 'size'.","Sanitize keys: keep only TOKEN characters before calling.","Validate each key with a TOKEN regex before building params."],"exampleFix":"// before\ncontent_disposition_header('attachment', params={'file name': 'x'})\n// after\ncontent_disposition_header('attachment', params={'filename': 'x'})","handlingStrategy":"validation","validationCode":"import re\nTOKEN_RE = re.compile(r\"^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$\")\ndef safe_params(p):\n    for k in p:\n        if not TOKEN_RE.fullmatch(k):\n            raise ValueError(f'bad param key {k!r}')\n    return p","typeGuard":"def is_valid_param_key(k) -> bool:\n    import re\n    return bool(re.fullmatch(r\"[!#$%&'*+\\-.^_`|~0-9A-Za-z]+\", k))","tryCatchPattern":null,"preventionTips":["Use simple lowercase token keys (name, filename, size).","Sanitize dynamic keys to TOKEN characters.","Never allow empty strings or spaces in parameter keys."],"tags":["content-disposition","validation","mime","headers"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}