Skip to content

Commit 01e93f5

Browse files
authored
Add caching tests (#94)
1 parent 8701f97 commit 01e93f5

18 files changed

Lines changed: 1333 additions & 10 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
title: Capabilities
3+
description: "Capabilities — Http11Probe documentation"
4+
weight: 12
5+
sidebar:
6+
open: false
7+
---
8+
9+
Capability tests probe optional HTTP features that servers may or may not implement. Unlike compliance tests, these are **unscored** — they map what each server supports rather than what it fails at.
10+
11+
## Scoring
12+
13+
All capability tests are **unscored**:
14+
15+
- **Pass** — Server correctly supports the feature
16+
- **Warn** — Server does not support the feature (not a failure)
17+
- **Fail** — Only for actual errors (unexpected status codes, connection errors)
18+
19+
## Conditional Requests (Caching)
20+
21+
These tests check whether the server supports ETag and Last-Modified based conditional requests (RFC 9110 §13).
22+
23+
{{< cards >}}
24+
{{< card link="etag-304" title="ETAG-304" subtitle="ETag conditional GET returns 304 Not Modified." >}}
25+
{{< card link="last-modified-304" title="LAST-MODIFIED-304" subtitle="Last-Modified conditional GET returns 304 Not Modified." >}}
26+
{{< card link="etag-in-304" title="ETAG-IN-304" subtitle="304 response includes ETag header." >}}
27+
{{< card link="inm-precedence" title="INM-PRECEDENCE" subtitle="If-None-Match takes precedence over If-Modified-Since." >}}
28+
{{< card link="inm-wildcard" title="INM-WILDCARD" subtitle="If-None-Match: * on existing resource returns 304." >}}
29+
{{< /cards >}}
30+
31+
## Conditional Request Edge Cases
32+
33+
These tests probe how servers handle invalid or unusual conditional headers — future dates, garbage values, unquoted ETags, and weak comparison (RFC 9110 §13).
34+
35+
{{< cards >}}
36+
{{< card link="ims-future" title="IMS-FUTURE" subtitle="If-Modified-Since with future date ignored." >}}
37+
{{< card link="ims-invalid" title="IMS-INVALID" subtitle="If-Modified-Since with garbage date ignored." >}}
38+
{{< card link="inm-unquoted" title="INM-UNQUOTED" subtitle="If-None-Match with unquoted ETag." >}}
39+
{{< card link="etag-weak" title="ETAG-WEAK" subtitle="Weak ETag comparison for GET." >}}
40+
{{< /cards >}}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
title: "ETAG-304"
3+
description: "CAP-ETAG-304 capability test documentation"
4+
weight: 10
5+
---
6+
7+
| | |
8+
|---|---|
9+
| **Test ID** | `CAP-ETAG-304` |
10+
| **Category** | Capabilities |
11+
| **Type** | Sequence (2 steps) |
12+
| **Scored** | No |
13+
| **RFC** | [RFC 9110 §13.1.2](https://www.rfc-editor.org/rfc/rfc9110#section-13.1.2) |
14+
| **RFC Level** | SHOULD |
15+
| **Expected** | `304` |
16+
17+
## What it does
18+
19+
This is a **sequence test** — it sends two requests on the same TCP connection to test ETag-based conditional request handling.
20+
21+
### Step 1: Initial GET (capture ETag)
22+
23+
```http
24+
GET / HTTP/1.1\r\n
25+
Host: localhost:8080\r\n
26+
Connection: keep-alive\r\n
27+
\r\n
28+
```
29+
30+
Captures the `ETag` header from the response for use in step 2.
31+
32+
### Step 2: Conditional GET (If-None-Match)
33+
34+
```http
35+
GET / HTTP/1.1\r\n
36+
Host: localhost:8080\r\n
37+
If-None-Match: "abc123"\r\n
38+
\r\n
39+
```
40+
41+
Sends the captured ETag value in an `If-None-Match` header. If the resource hasn't changed, the server should return `304 Not Modified`.
42+
43+
## What the RFC says
44+
45+
> "An origin server MUST use the strong comparison function when comparing entity-tags for If-None-Match, because the client intends to use the cached representation." — RFC 9110 §13.1.2
46+
47+
> "If the field value is '*', the condition is false if the origin server has a current representation for the target resource." — RFC 9110 §13.1.2
48+
49+
## Why it matters
50+
51+
ETag-based conditional requests are the most reliable caching mechanism in HTTP. They enable efficient revalidation without relying on timestamps, which can be unreliable across servers or after deployments.
52+
53+
## Verdicts
54+
55+
- **Pass** — Step 2 returns `304 Not Modified`
56+
- **Warn** — Server does not include ETag in step 1, or returns `200` in step 2 (no conditional support)
57+
- **Fail** — Unexpected error (non-2xx/304 response)
58+
59+
## Sources
60+
61+
- [RFC 9110 §13.1.2](https://www.rfc-editor.org/rfc/rfc9110#section-13.1.2)
62+
- [RFC 9110 §8.8.3](https://www.rfc-editor.org/rfc/rfc9110#section-8.8.3)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
title: "ETAG-IN-304"
3+
description: "CAP-ETAG-IN-304 capability test documentation"
4+
weight: 12
5+
---
6+
7+
| | |
8+
|---|---|
9+
| **Test ID** | `CAP-ETAG-IN-304` |
10+
| **Category** | Capabilities |
11+
| **Type** | Sequence (2 steps) |
12+
| **Scored** | No |
13+
| **RFC** | [RFC 9110 §15.4.5](https://www.rfc-editor.org/rfc/rfc9110#section-15.4.5) |
14+
| **RFC Level** | SHOULD |
15+
| **Expected** | `304` with ETag |
16+
17+
## What it does
18+
19+
This is a **sequence test** — it verifies that a `304 Not Modified` response includes the `ETag` header, allowing clients to update their cached validators.
20+
21+
### Step 1: Initial GET (capture ETag)
22+
23+
```http
24+
GET / HTTP/1.1\r\n
25+
Host: localhost:8080\r\n
26+
Connection: keep-alive\r\n
27+
\r\n
28+
```
29+
30+
Captures the `ETag` header from the response.
31+
32+
### Step 2: Conditional GET (If-None-Match)
33+
34+
```http
35+
GET / HTTP/1.1\r\n
36+
Host: localhost:8080\r\n
37+
If-None-Match: "abc123"\r\n
38+
\r\n
39+
```
40+
41+
Sends the captured ETag. If the server returns `304`, this test checks whether the `ETag` header is present in that response.
42+
43+
## What the RFC says
44+
45+
> "A server generating a 304 response MUST generate any of the following header fields that would have been sent in a 200 (OK) response to the same request: ... ETag" — RFC 9110 §15.4.5
46+
47+
## Why it matters
48+
49+
Including the ETag in a `304` response lets clients confirm which representation they have cached and update their stored validator. Without it, clients may lose track of the ETag and fall back to unconditional requests.
50+
51+
## Verdicts
52+
53+
- **Pass** — Step 2 returns `304` with an ETag header
54+
- **Warn** — Server does not support ETags, or returns `304` without an ETag header
55+
- **Fail** — Unexpected error (non-2xx/304 response)
56+
57+
## Sources
58+
59+
- [RFC 9110 §15.4.5](https://www.rfc-editor.org/rfc/rfc9110#section-15.4.5)
60+
- [RFC 9110 §8.8.3](https://www.rfc-editor.org/rfc/rfc9110#section-8.8.3)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
title: "ETAG-WEAK"
3+
description: "CAP-ETAG-WEAK capability test documentation"
4+
weight: 18
5+
---
6+
7+
| | |
8+
|---|---|
9+
| **Test ID** | `CAP-ETAG-WEAK` |
10+
| **Category** | Capabilities |
11+
| **Type** | Sequence (2 steps) |
12+
| **Scored** | No |
13+
| **RFC** | [RFC 9110 §13.1.2](https://www.rfc-editor.org/rfc/rfc9110#section-13.1.2) |
14+
| **RFC Level** | SHOULD |
15+
| **Expected** | `304` |
16+
17+
## What it does
18+
19+
This is a **sequence test** — it captures the server's ETag and resends it with a `W/` weak prefix in `If-None-Match` to test whether the server uses the weak comparison function for GET requests.
20+
21+
### Step 1: Initial GET (capture ETag)
22+
23+
```http
24+
GET / HTTP/1.1\r\n
25+
Host: localhost:8080\r\n
26+
Connection: keep-alive\r\n
27+
\r\n
28+
```
29+
30+
Captures the `ETag` header from the response. If the ETag is strong (e.g., `"abc123"`), step 2 will prepend `W/` to make it weak (`W/"abc123"`). If already weak, it is sent as-is.
31+
32+
### Step 2: Conditional GET (If-None-Match: W/etag)
33+
34+
```http
35+
GET / HTTP/1.1\r\n
36+
Host: localhost:8080\r\n
37+
If-None-Match: W/"abc123"\r\n
38+
\r\n
39+
```
40+
41+
The weak ETag should still match via the weak comparison function, which only compares the opaque-tag portion.
42+
43+
## What the RFC says
44+
45+
> "A recipient MUST use the weak comparison function when comparing entity-tags for If-None-Match." — RFC 9110 §13.1.2
46+
47+
The weak comparison function is defined as: "two entity-tags are equivalent if their opaque-tags match character-by-character, regardless of either or both being tagged as 'weak'."
48+
49+
## Why it matters
50+
51+
GET conditional requests must use weak comparison. A server that only does byte-for-byte matching of the full ETag string (including `W/` prefix) will fail to match weak ETags, causing unnecessary full responses for cacheable content.
52+
53+
## Verdicts
54+
55+
- **Pass** — Step 2 returns `304` (weak comparison matched)
56+
- **Warn** — No ETag in step 1, or step 2 returns `200` (server didn't use weak comparison)
57+
- **Fail** — Unexpected error (non-2xx/304 response)
58+
59+
## Sources
60+
61+
- [RFC 9110 §13.1.2](https://www.rfc-editor.org/rfc/rfc9110#section-13.1.2)
62+
- [RFC 9110 §8.8.3.2](https://www.rfc-editor.org/rfc/rfc9110#section-8.8.3.2)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
title: "IMS-FUTURE"
3+
description: "CAP-IMS-FUTURE capability test documentation"
4+
weight: 15
5+
---
6+
7+
| | |
8+
|---|---|
9+
| **Test ID** | `CAP-IMS-FUTURE` |
10+
| **Category** | Capabilities |
11+
| **Type** | Sequence (2 steps) |
12+
| **Scored** | No |
13+
| **RFC** | [RFC 9110 §13.1.3](https://www.rfc-editor.org/rfc/rfc9110#section-13.1.3) |
14+
| **RFC Level** | SHOULD |
15+
| **Expected** | `200` |
16+
17+
## What it does
18+
19+
This is a **sequence test** — it sends an `If-Modified-Since` header with a date far in the future to check whether the server correctly ignores it.
20+
21+
### Step 1: Initial GET (confirm 2xx)
22+
23+
```http
24+
GET / HTTP/1.1\r\n
25+
Host: localhost:8080\r\n
26+
Connection: keep-alive\r\n
27+
\r\n
28+
```
29+
30+
Verifies the resource exists and returns a success response.
31+
32+
### Step 2: Conditional GET (If-Modified-Since: future date)
33+
34+
```http
35+
GET / HTTP/1.1\r\n
36+
Host: localhost:8080\r\n
37+
If-Modified-Since: Thu, 01 Jan 2099 00:00:00 GMT\r\n
38+
\r\n
39+
```
40+
41+
Sends a future date. A compliant server must ignore an `If-Modified-Since` value that is later than the server's current time and return the resource normally.
42+
43+
## What the RFC says
44+
45+
> "A recipient MUST ignore If-Modified-Since if the field value is not a valid HTTP-date, or if the field value is a date in the future (compared to the server's current time)." — RFC 9110 §13.1.3
46+
47+
## Why it matters
48+
49+
A server that blindly compares dates without checking whether the date is in the future could incorrectly return `304 Not Modified` for every request with a future timestamp, allowing cache-poisoning or stale-content attacks.
50+
51+
## Verdicts
52+
53+
- **Pass** — Step 2 returns `200` (correctly ignores future date)
54+
- **Warn** — Server returns `304` (didn't validate the date against current time)
55+
- **Fail** — Unexpected error (non-2xx/304 response)
56+
57+
## Sources
58+
59+
- [RFC 9110 §13.1.3](https://www.rfc-editor.org/rfc/rfc9110#section-13.1.3)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
title: "IMS-INVALID"
3+
description: "CAP-IMS-INVALID capability test documentation"
4+
weight: 16
5+
---
6+
7+
| | |
8+
|---|---|
9+
| **Test ID** | `CAP-IMS-INVALID` |
10+
| **Category** | Capabilities |
11+
| **Type** | Sequence (2 steps) |
12+
| **Scored** | No |
13+
| **RFC** | [RFC 9110 §13.1.3](https://www.rfc-editor.org/rfc/rfc9110#section-13.1.3) |
14+
| **RFC Level** | SHOULD |
15+
| **Expected** | `200` |
16+
17+
## What it does
18+
19+
This is a **sequence test** — it sends an `If-Modified-Since` header with an unparseable garbage value to check whether the server correctly ignores it.
20+
21+
### Step 1: Initial GET (confirm 2xx)
22+
23+
```http
24+
GET / HTTP/1.1\r\n
25+
Host: localhost:8080\r\n
26+
Connection: keep-alive\r\n
27+
\r\n
28+
```
29+
30+
Verifies the resource exists and returns a success response.
31+
32+
### Step 2: Conditional GET (If-Modified-Since: garbage)
33+
34+
```http
35+
GET / HTTP/1.1\r\n
36+
Host: localhost:8080\r\n
37+
If-Modified-Since: not-a-date\r\n
38+
\r\n
39+
```
40+
41+
Sends a value that is not a valid HTTP-date. A compliant server must ignore the header and return the resource normally.
42+
43+
## What the RFC says
44+
45+
> "A recipient MUST ignore If-Modified-Since if the field value is not a valid HTTP-date." — RFC 9110 §13.1.3
46+
47+
## Why it matters
48+
49+
If a server treats an unparseable date as "very old" and returns `304`, it could cause clients to serve stale cached content. Correct behavior is to ignore the invalid header entirely.
50+
51+
## Verdicts
52+
53+
- **Pass** — Step 2 returns `200` (correctly ignores invalid date)
54+
- **Warn** — Server returns `304` (treated garbage as a valid date)
55+
- **Fail** — Unexpected error (non-2xx/304 response)
56+
57+
## Sources
58+
59+
- [RFC 9110 §13.1.3](https://www.rfc-editor.org/rfc/rfc9110#section-13.1.3)

0 commit comments

Comments
 (0)