Skip to content

A 304 is answered for any URI whose validator exists in the ETag pool #197

Description

@koriym

HttpCache::isNotModified() answers 304 whenever the offered validator exists anywhere in the ETag pool. It is not scoped to the resource being requested.

$about   = $resource->get('page://self/help/about');    // ETag "927897379"
$privacy = $resource->get('page://self/help/privacy');  // ETag "3259201453"

// asking for /help/privacy while offering /help/about's validator
$httpCache->isNotModified(['HTTP_IF_NONE_MATCH' => '"927897379"', 'REQUEST_URI' => '/help/privacy']);
// => true (304)

The client is told its copy of /help/privacy is current when the server has never served that copy. RFC 9110 §13.1.2 scopes If-None-Match to the selected representation of the target resource.

ResourceStorage::hasEtag() has been a set-membership test since 2018 (645f9a7), and the ETag pool stores the validator as its key with no URI beside it.

Reachability

A client that returns the validator it was given cannot reach this: EtagSetter derives the value from $ro::class . $etag . $ro->uri, so each URI has its own. What reaches it is a client or intermediary that mixes validators across URIs, or one that offers a value it did not obtain from this server (the space is a crc32).

Why the obvious fix is worse

Storing the URI in the entry and comparing it with REQUEST_URI breaks every routed application: BeMart maps /products/5 to page://self/product?id=5, so a legitimate revalidation would compare paths that do not match and answer 200 forever.

The decision is made in Bootstrap before routing precisely so a 304 costs no resource run, and that is where the resource URI does not exist yet. A correct fix needs the routed URI at the decision point - HttpCacheInterface::isNotModified(array $server) is bear/sunday's contract - and then hasEtag() can be scoped to it.

Filing here because the pool and the decision live in this package. Recorded in an application's cache oracle as a known defect so it reports until it closes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions