mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-25 22:40:44 +01:00
* filer: match If-Match and If-None-Match against "*" and ETag lists checkPreconditions compared the whole header as one ETag, so "*", a list of ETags and a weak W/ tag in If-None-Match never matched (RFC 9110 13.1.1, 13.1.2). * filer: split etag lists only outside quoted tags A comma inside a quoted opaque tag is a legal etagc, not a list separator; strings.Split cut such tags into fragments that CanonicalizeETag could turn into a false match. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: match repeated If-Match and If-None-Match header lines Header.Get reads only the first field line, so a match on a later repeated header was missed; join all field values like any other comma-separated list. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: ignore empty elements in etag precondition lists * filer: let a present-but-empty precondition line suppress the date check --------- Co-authored-by: user.email <chrislusf@users.noreply.github.com> Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: chrislusf <chris.lu@gmail.com>
116 lines
3.9 KiB
Go
116 lines
3.9 KiB
Go
package weed_server
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/filer"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
)
|
|
|
|
func TestCheckPreconditionsETagLists(t *testing.T) {
|
|
entry := &filer.Entry{Attr: filer.Attr{Mtime: time.Unix(1700000000, 0), Md5: []byte{0xb1, 0x94}}}
|
|
const etag = `"b194"`
|
|
|
|
tests := []struct {
|
|
header string
|
|
values []string
|
|
want int
|
|
}{
|
|
{"If-None-Match", []string{etag}, http.StatusNotModified},
|
|
{"If-None-Match", []string{`*`}, http.StatusNotModified},
|
|
{"If-None-Match", []string{`W/` + etag}, http.StatusNotModified},
|
|
{"If-None-Match", []string{`"x", ` + etag}, http.StatusNotModified},
|
|
{"If-None-Match", []string{`"x"`, etag}, http.StatusNotModified},
|
|
{"If-None-Match", []string{`"x"`}, http.StatusOK},
|
|
{"If-None-Match", []string{`"b194,old"`}, http.StatusOK},
|
|
{"If-Match", []string{etag}, http.StatusOK},
|
|
{"If-Match", []string{`*`}, http.StatusOK},
|
|
{"If-Match", []string{`"x", ` + etag}, http.StatusOK},
|
|
{"If-Match", []string{`"x"`, etag}, http.StatusOK},
|
|
{"If-Match", []string{`W/` + etag}, http.StatusPreconditionFailed},
|
|
{"If-Match", []string{`"x"`}, http.StatusPreconditionFailed},
|
|
{"If-Match", []string{`"b194,old"`}, http.StatusPreconditionFailed},
|
|
}
|
|
for _, tt := range tests {
|
|
r := httptest.NewRequest(http.MethodGet, "/d/f.txt", nil)
|
|
for _, v := range tt.values {
|
|
r.Header.Add(tt.header, v)
|
|
}
|
|
w := httptest.NewRecorder()
|
|
got := http.StatusOK
|
|
if checkPreconditions(w, r, entry) {
|
|
got = w.Code
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("%s: %q -> %d, want %d", tt.header, tt.values, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCheckPreconditionsPresentButEmptyHeaders(t *testing.T) {
|
|
entry := &filer.Entry{Attr: filer.Attr{Mtime: time.Unix(1700000000, 0), Md5: []byte{0xb1, 0x94}}}
|
|
before := time.Unix(1700000000-60, 0).UTC().Format(http.TimeFormat)
|
|
after := time.Unix(1700000000+60, 0).UTC().Format(http.TimeFormat)
|
|
|
|
// An empty If-Match line is still an If-Match: it matches nothing, and
|
|
// If-Unmodified-Since stays ignored.
|
|
r := httptest.NewRequest(http.MethodGet, "/d/f.txt", nil)
|
|
r.Header.Set("If-Match", "")
|
|
r.Header.Set("If-Unmodified-Since", after)
|
|
if w := httptest.NewRecorder(); !checkPreconditions(w, r, entry) || w.Code != http.StatusPreconditionFailed {
|
|
t.Errorf("empty If-Match -> %d, want 412", w.Code)
|
|
}
|
|
|
|
// An empty If-None-Match line matches nothing and suppresses
|
|
// If-Modified-Since, so the request proceeds.
|
|
r = httptest.NewRequest(http.MethodGet, "/d/f.txt", nil)
|
|
r.Header.Set("If-None-Match", "")
|
|
r.Header.Set("If-Modified-Since", after)
|
|
if checkPreconditions(httptest.NewRecorder(), r, entry) {
|
|
t.Error("empty If-None-Match with matching If-Modified-Since -> 304, want 200")
|
|
}
|
|
|
|
r = httptest.NewRequest(http.MethodGet, "/d/f.txt", nil)
|
|
r.Header.Set("If-Modified-Since", before)
|
|
if checkPreconditions(httptest.NewRecorder(), r, entry) {
|
|
t.Error("old If-Modified-Since alone -> 304, want 200")
|
|
}
|
|
}
|
|
|
|
func TestCheckPreconditionsIgnoresEmptyETagListElements(t *testing.T) {
|
|
entry := &filer.Entry{
|
|
Attr: filer.Attr{Mtime: time.Unix(1700000000, 0)},
|
|
Remote: &filer_pb.RemoteEntry{RemoteSize: 1},
|
|
}
|
|
|
|
tests := []struct {
|
|
header string
|
|
values []string
|
|
want int
|
|
}{
|
|
{"If-None-Match", []string{`"x",`}, http.StatusOK},
|
|
{"If-None-Match", []string{`"x",,"y"`}, http.StatusOK},
|
|
{"If-None-Match", []string{`""`}, http.StatusNotModified},
|
|
{"If-None-Match", []string{`W/""`}, http.StatusNotModified},
|
|
{"If-Match", []string{`"x",`}, http.StatusPreconditionFailed},
|
|
{"If-Match", []string{`""`}, http.StatusOK},
|
|
}
|
|
for _, tt := range tests {
|
|
r := httptest.NewRequest(http.MethodGet, "/d/f.txt", nil)
|
|
for _, v := range tt.values {
|
|
r.Header.Add(tt.header, v)
|
|
}
|
|
w := httptest.NewRecorder()
|
|
got := http.StatusOK
|
|
if checkPreconditions(w, r, entry) {
|
|
got = w.Code
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("%s: %q -> %d, want %d", tt.header, tt.values, got, tt.want)
|
|
}
|
|
}
|
|
}
|