Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions httpcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -1145,7 +1145,7 @@ func isActuallyStale(respHeaders http.Header) bool {
return true // No date means we can't determine freshness, treat as stale
}

currentAge := clock.since(date)
currentAge := clampedAge(date)
lifetime := calculateLifetime(respCacheControl, respHeaders, date)

// Check if stale-while-revalidate extends freshness
Expand Down Expand Up @@ -1270,6 +1270,15 @@ type timer interface {

var clock timer = &realClock{}

// clampedAge returns now - date clamped to >= 0, the max(0, ...) of apparent_age
// in RFC 9111 Section 4.2.3, so clock skew cannot produce a negative current_age.
func clampedAge(date time.Time) time.Duration {
if age := clock.since(date); age > 0 {
return age
}
return 0
}

// getFreshness will return one of fresh/stale/transparent based on the cache-control
// values of the request and the response
//
Expand All @@ -1294,7 +1303,7 @@ func getFreshness(respHeaders, reqHeaders http.Header) (freshness int) {
if err != nil {
return stale
}
currentAge := clock.since(date)
currentAge := clampedAge(date)

// Calculate response lifetime
lifetime := calculateLifetime(respCacheControl, respHeaders, date)
Expand Down Expand Up @@ -1350,7 +1359,7 @@ func checkStaleIfErrorLifetime(respHeaders http.Header, lifetime time.Duration)
if err != nil {
return false
}
currentAge := clock.since(date)
currentAge := clampedAge(date)
return lifetime > currentAge
}

Expand Down
21 changes: 21 additions & 0 deletions httpcache_clockskew_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package httpcache

import (
"net/http"
"testing"
"time"
)

// TestFreshnessFutureDateClockSkew verifies a future Date header (clock skew) on a response
// with no freshness info is stale: RFC 9111 §4.2.3 clamps apparent_age to max(0, ...).
func TestFreshnessFutureDateClockSkew(t *testing.T) {
resetTest()

respHeaders := http.Header{}
// Date 2s in the future, no Cache-Control and no Expires (lifetime 0).
respHeaders.Set("Date", time.Now().Add(2*time.Second).UTC().Format(time.RFC1123))

if got := getFreshness(respHeaders, http.Header{}); got == fresh {
t.Fatalf("future Date with no freshness info treated as fresh; want stale (RFC 9111 §4.2.3); got %s", freshnessString(got))
}
Comment on lines +15 to +20
}
Loading