aio-libs/aiohttp · error · ValueError
Value {value!r} is not a valid etag. Maybe it contains '"'?
Error message
Value {value!r} is not a valid etag. Maybe it contains '"'? What it means
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).
Source
Thrown at aiohttp/helpers.py:1146
# https://tools.ietf.org/html/rfc7232#section-2.3
_ETAGC = r"[!\x23-\x7E\x80-\xff]+"
_ETAGC_RE = re.compile(_ETAGC)
_QUOTED_ETAG = rf'(W/)?"({_ETAGC})"'
QUOTED_ETAG_RE = re.compile(_QUOTED_ETAG)
LIST_QUOTED_ETAG_RE = re.compile(rf"({_QUOTED_ETAG})(?:\s*,\s*|$)|(.)")
ETAG_ANY = "*"
@frozen_dataclass_decorator
class ETag:
value: str
is_weak: bool = False
def validate_etag_value(value: str) -> None:
if value != ETAG_ANY and not _ETAGC_RE.fullmatch(value):
raise ValueError(
f"Value {value!r} is not a valid etag. Maybe it contains '\"'?"
)
def parse_http_date(date_str: str | None) -> datetime.datetime | None:
"""Process a date string, return a datetime object"""
if date_str is not None:
timetuple = parsedate(date_str)
if timetuple is not None:
with suppress(ValueError):
return datetime.datetime(*timetuple[:6], tzinfo=datetime.timezone.utc)
return None
@functools.lru_cache
def must_be_empty_body(method: str, code: int) -> bool:
"""Check if a request must return an empty body."""
return (View on GitHub (pinned to c0ef574e29)
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.
Example fix
// before
validate_etag_value('"abc123"') # raises
// after
validate_etag_value('abc123') Defensive patterns
Strategy: validation
Validate before calling
import re
_ETAGC_RE = re.compile(r'[!#-~\x80-\xff]+')
def normalize_etag(value):
if value == '*':
return value
v = value[2:] if value.startswith('W/') else value
v = v.strip('"')
if not _ETAGC_RE.fullmatch(v):
raise ValueError(f'invalid etag {value!r}')
return v Type guard
def is_valid_etag(value) -> bool:
import re
return value == '*' or bool(re.fullmatch(r'[!#-~\x80-\xff]+', value)) Try / catch
try:
validate_etag_value(value)
except ValueError:
value = value.strip('"').removeprefix('W/').strip('"') Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid Content-Length header: {content_length_hdr!r}
- bad content disposition type {disptype!r}
- bad content disposition parameter {key!r}={val!r}
- Missing 'Host' header in request.
- Unsupported etag type: {type(value)}. etag must be str, ETag
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/4367850a834bfd36.json.
Report an issue: GitHub.