{"id":"4367850a834bfd36","repo":"aio-libs/aiohttp","slug":"value-value-r-is-not-a-valid-etag-maybe-it-cont","errorCode":null,"errorMessage":"Value {value!r} is not a valid etag. Maybe it contains '\"'?","messagePattern":"Value (.+?) is not a valid etag\\. Maybe it contains '\"'\\?","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/helpers.py","lineNumber":1146,"sourceCode":"# https://tools.ietf.org/html/rfc7232#section-2.3\n_ETAGC = r\"[!\\x23-\\x7E\\x80-\\xff]+\"\n_ETAGC_RE = re.compile(_ETAGC)\n_QUOTED_ETAG = rf'(W/)?\"({_ETAGC})\"'\nQUOTED_ETAG_RE = re.compile(_QUOTED_ETAG)\nLIST_QUOTED_ETAG_RE = re.compile(rf\"({_QUOTED_ETAG})(?:\\s*,\\s*|$)|(.)\")\n\nETAG_ANY = \"*\"\n\n\n@frozen_dataclass_decorator\nclass ETag:\n    value: str\n    is_weak: bool = False\n\n\ndef validate_etag_value(value: str) -> None:\n    if value != ETAG_ANY and not _ETAGC_RE.fullmatch(value):\n        raise ValueError(\n            f\"Value {value!r} is not a valid etag. Maybe it contains '\\\"'?\"\n        )\n\n\ndef parse_http_date(date_str: str | None) -> datetime.datetime | None:\n    \"\"\"Process a date string, return a datetime object\"\"\"\n    if date_str is not None:\n        timetuple = parsedate(date_str)\n        if timetuple is not None:\n            with suppress(ValueError):\n                return datetime.datetime(*timetuple[:6], tzinfo=datetime.timezone.utc)\n    return None\n\n\n@functools.lru_cache\ndef must_be_empty_body(method: str, code: int) -> bool:\n    \"\"\"Check if a request must return an empty body.\"\"\"\n    return (","sourceCodeStart":1128,"sourceCodeEnd":1164,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/helpers.py#L1128-L1164","documentation":"Raised by validate_etag_value when value is not '*' and does not match the _ETAGC regex ([!#-~\\x80-\\xff]+), i.e. it contains a literal double-quote or other forbidden characters. ETags are validated before being placed in If-None-Match/If-Match; the surrounding quotes are added by the caller, so an embedded '\"' is invalid (ValueError).","triggerScenarios":"Passing a raw quoted etag like '\"abc123\"' or 'W/\"abc\"' to validate_etag_value, or an etag containing spaces/control chars. The function expects the bare opaque-tag without quotes or the weak prefix.","commonSituations":"Forwarding an ETag header value verbatim from a response into a conditional request without stripping quotes; concatenating user input with '\"'; treating 'W/' prefix as part of the value.","solutions":["Strip surrounding quotes and any 'W/' weak prefix before validating.","Pass '*' for the any-match wildcard.","Use the parsed ETag dataclass (helpers.ETag) rather than hand-formatting."],"exampleFix":"// before\nvalidate_etag_value('\"abc123\"')  # raises\n// after\nvalidate_etag_value('abc123')","handlingStrategy":"validation","validationCode":"import re\n_ETAGC_RE = re.compile(r'[!#-~\\x80-\\xff]+')\ndef normalize_etag(value):\n    if value == '*':\n        return value\n    v = value[2:] if value.startswith('W/') else value\n    v = v.strip('\"')\n    if not _ETAGC_RE.fullmatch(v):\n        raise ValueError(f'invalid etag {value!r}')\n    return v","typeGuard":"def is_valid_etag(value) -> bool:\n    import re\n    return value == '*' or bool(re.fullmatch(r'[!#-~\\x80-\\xff]+', value))","tryCatchPattern":"try:\n    validate_etag_value(value)\nexcept ValueError:\n    value = value.strip('\"').removeprefix('W/').strip('\"')","preventionTips":["Strip surrounding quotes and 'W/' before validating an etag.","Use '*' for the wildcard match-any etag.","Prefer aiohttp.helpers.ETag parsing over hand-rolled validation."],"tags":["etag","validation","headers","conditional-request"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}