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

  1. Strip surrounding quotes and any 'W/' weak prefix before validating.
  2. Pass '*' for the any-match wildcard.
  3. 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

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


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