A conditional request is not a set of tests. It is a walk.
RFC 9110 §13.2.2 lays six fields out in a fixed order with two else branches in it. Step 2 is reached only when If-Match is absent; step 4 only when If-None-Match is absent. So a header that is present, well formed, and would have failed is never consulted at all — and the gate is presence, not validity:
// step 3 : If-None-Match
// step 4 : If-Modified-Since -- ONLY when If-None-Match is absent,
// and the gate is inm.present, NOT inm.ok
An If-None-Match holding literal garbage silences a date that would have produced a 304. Evaluate it field by field: https://dev48.infy.uk/solve/day70-http-precondition-evaluator.html
One token, four status codes
A representation whose current validator is the strong tag "abc". In every row the client sends the weak spelling of that same tag, W/"abc", unchanged, byte for byte:
| where the same token sits | status |
|---|---|
If-None-Match on a GET |
304 |
If-Match |
412 |
If-Range |
200 |
If-None-Match on a POST |
200 |
If-None-Match on a GET that merely also carries Range
|
206 |
Nothing about the tag decides. The field name picks the comparison function, the method decides whether the precondition is evaluated at all, and an unrelated neighbour turns a 200 into a 206.
Why the wrong model ships
Evaluate every conditional and AND the results. That model is exactly right on all 7,680 grid cells where nothing is muzzled, and wrong on 22.50% of the 3,840 where something is — 864 cells. A suite that sends one conditional header at a time cannot distinguish the two implementations, and one at a time is precisely how conditional requests get tested.
The engine is differentially tested against a throwaway Apache httpd 2.4.41 that the verifier starts on a spare port and tears down again: 3,750 live requests, 0 status disagreements, all 341 Content-Range strings identical, all 176 GET bodies byte-identical, and all 63 multipart/byteranges part lists identical in count, offsets and order.
Two range facts fall out and both cost money. An invalid Range is ignored and the client gets 200 with the whole representation; a valid but unsatisfiable one is 416 and the client gets nothing — adjacent lines, opposite outcomes, routinely fused into one error path. And parts are emitted in request order rather than ascending, with touching ranges coalesced, so bytes=0-9,10-19,20-29 is three specifications and one part with no multipart wrapper at all.
What the measurement contradicted
Five corrections, and the best of them the live server could never have found. My first coalesce merged each new interval into the first part it overlapped and then stopped. That is right until one open-ended range bridges two parts that were not touching each other, and then it emits two adjacent parts that should have been one. Apache agreed with me on every request in the differential sweep, because nothing a real client sends has that shape. The byte-marking oracle — a boolean array marked byte by byte, sharing no line with the interval arithmetic — disagreed within the first few hundred fuzzed fields. Merging now runs to a fixpoint.
I also had the rfc850 fifty-year window backwards. The rule is one-directional: start in the reader's own century and step back while the result is more than fifty years ahead. It never steps forward. So a 1999-era recipient resolves 06-Nov-00 to 1900, not 2000 — the clause that fixed Y2K for HTTP dates cannot rescue a date that already lands in the past, which is the Y2K bug itself, preserved in the grammar. My assertion failed on 21 of the 100 two-digit years, and the engine was right.
79 reference cases, 273,479 in-page assertions and 126,280 in the verifier. One file, inline CSS, no external asset.
Part of a from-scratch series — one tool a day, all client-side, dependency-free engine: https://dev48.infy.uk/solvefromzero.php
Top comments (0)