From e20a3a829e74e40e69e8ef948a1027ac46f4f3b3 Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Thu, 11 Apr 2024 13:09:05 +0200 Subject: [PATCH] Implement precondition checking for objects with no ETag. --- webserver/precondition.go | 50 ++++++++++++++++++++++++++++++++++ webserver/precondition_test.go | 45 ++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/webserver/precondition.go b/webserver/precondition.go index d13da32..a0f1012 100644 --- a/webserver/precondition.go +++ b/webserver/precondition.go @@ -70,6 +70,34 @@ func etagMatch(etag, header string) bool { return false } +// etagMatchNoEtag evaluates preconditions for an object with no ETag. +func etagMatchNoEtag(exists bool, header string) bool { + if header == "" { + return false + } + + for { + header = strings.TrimLeft(header, " \t\n\r") + if len(header) == 0 { + break + } + if header[0] == ',' { + header = header[1:] + continue + } + if header[0] == '*' { + return exists + } + e, remain := scanETag(header) + if e == "" { + break + } + header = remain + } + + return false +} + func writeNotModified(w http.ResponseWriter) { // RFC 7232 section 4.1: // a sender SHOULD NOT generate representation metadata other than the @@ -108,3 +136,25 @@ func checkPreconditions(w http.ResponseWriter, r *http.Request, etag string) (do return false } + +// checkPreconditions evaluates request preconditions for an object with no ETag. +// exists indicates whether the object exists. +func checkPreconditionsNoEtag(w http.ResponseWriter, r *http.Request, exists bool) (done bool) { + im := r.Header.Get("If-Match") + if im != "" && !etagMatchNoEtag(exists, im) { + w.WriteHeader(http.StatusPreconditionFailed) + return true + } + inm := r.Header.Get("If-None-Match") + if inm != "" && etagMatchNoEtag(exists, inm) { + if r.Method == "GET" || r.Method == "HEAD" { + writeNotModified(w) + return true + } else { + w.WriteHeader(http.StatusPreconditionFailed) + return true + } + } + + return false +} diff --git a/webserver/precondition_test.go b/webserver/precondition_test.go index 5ed61b3..be80b7c 100644 --- a/webserver/precondition_test.go +++ b/webserver/precondition_test.go @@ -89,6 +89,8 @@ func TestCheckPreconditions(t *testing.T) { {"POST", `"123"`, ``, `"123"`, 412}, {"GET", `"123"`, ``, `"124"`, 0}, {"GET", `"123"`, ``, `*`, 304}, + {"POST", `"123"`, ``, `"124"`, 0}, + {"POST", `"123"`, ``, `*`, 412}, } for _, tst := range tests { @@ -112,3 +114,46 @@ func TestCheckPreconditions(t *testing.T) { } } } + +func TestCheckPreconditionsNoEtag(t *testing.T) { + var tests = []struct { + method string + exists bool + im, inm string + result int + }{ + {"GET", false, ``, ``, 0}, + {"GET", false, `*`, ``, 412}, + {"GET", false, ``, `*`, 0}, + {"POST", false, `*`, ``, 412}, + {"POST", false, ``, `*`, 0}, + {"GET", true, ``, ``, 0}, + {"GET", true, `"124"`, ``, 412}, + {"POST", true, `"124"`, ``, 412}, + {"GET", true, `*`, ``, 0}, + {"GET", true, ``, `*`, 304}, + {"POST", true, `*`, ``, 0}, + {"POST", true, ``, `*`, 412}, + } + + for _, tst := range tests { + var w testWriter + h := make(http.Header) + if tst.im != "" { + h.Set("If-Match", tst.im) + } + if tst.inm != "" { + h.Set("If-None-Match", tst.inm) + } + r := http.Request{ + Method: tst.method, + Header: h, + } + done := checkPreconditionsNoEtag(&w, &r, tst.exists) + if done != (tst.result != 0) || w.statusCode != tst.result { + t.Errorf("%v %#v %#v: got %v, expected %v", + tst.exists, tst.im, tst.inm, + w.statusCode, tst.result) + } + } +}