Skip to content

Commit 8ad554e

Browse files
Dylan Cantbarkyq
authored andcommitted
ConditionalMatch with multiple etags
1 parent 3cc7466 commit 8ad554e

File tree

3 files changed

+91
-17
lines changed

3 files changed

+91
-17
lines changed

fs_local.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -126,19 +126,20 @@ func (fs LocalFileSystem) Create(ctx context.Context, name string, body io.ReadC
126126
etag = fi.ETag
127127
}
128128

129-
if opts.IfMatch.IsSet() {
130-
if ok, err := opts.IfMatch.MatchETag(etag); err != nil {
131-
return nil, false, NewHTTPError(http.StatusBadRequest, err)
132-
} else if !ok {
133-
return nil, false, NewHTTPError(http.StatusPreconditionFailed, fmt.Errorf("If-Match condition failed"))
134-
}
129+
if isSet, ok, err := opts.IfMatch.MatchETag(etag); !isSet {
130+
// not set so continue
131+
} else if err != nil {
132+
return nil, false, NewHTTPError(http.StatusBadRequest, err)
133+
} else if isSet && !ok {
134+
return nil, false, NewHTTPError(http.StatusPreconditionFailed, fmt.Errorf("If-Match condition failed"))
135135
}
136-
if opts.IfNoneMatch.IsSet() {
137-
if ok, err := opts.IfNoneMatch.MatchETag(etag); err != nil {
138-
return nil, false, NewHTTPError(http.StatusBadRequest, err)
139-
} else if ok {
140-
return nil, false, NewHTTPError(http.StatusPreconditionFailed, fmt.Errorf("If-None-Match condition failed"))
141-
}
136+
137+
if isSet, ok, err := opts.IfNoneMatch.MatchETag(etag); !isSet {
138+
// not set so continue
139+
} else if err != nil {
140+
return nil, false, NewHTTPError(http.StatusBadRequest, err)
141+
} else if isSet && ok {
142+
return nil, false, NewHTTPError(http.StatusPreconditionFailed, fmt.Errorf("If-None-Match condition failed"))
142143
}
143144

144145
wc, err := os.Create(p)

webdav.go

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package webdav
55

66
import (
7+
"strings"
78
"time"
89

910
"github.com/emersion/go-webdav/internal"
@@ -54,10 +55,36 @@ func (val ConditionalMatch) ETag() (string, error) {
5455
return string(e), nil
5556
}
5657

57-
func (val ConditionalMatch) MatchETag(etag string) (bool, error) {
58-
if val.IsWildcard() {
59-
return true, nil
58+
// MatchETag checks if the ETag provided matches any of the ETags in the ConditionalMatch header value.
59+
//
60+
// Parameters:
61+
// - etag: The ETag to match against.
62+
//
63+
// Returns:
64+
// - isSet: Indicates if the ConditionalMatch has any ETags set, or is wildcard.
65+
// - match: True if the etag matches any of the ETags in ConditionalMatch, false otherwise.
66+
// - err: An error if there was a problem parsing one of the ETags.
67+
// If an error occurs during parsing, match will be set to false, but isSet will be true.
68+
// Callers should check for a non-nil error to ensure the match result is valid.
69+
//
70+
// The function returns early if no ETags are set (isSet is false) or if a wildcard (*) is used,
71+
// in which case all ETags match. For multiple ETags, it checks each one until a match is found or all are checked.
72+
func (val ConditionalMatch) MatchETag(etag string) (isSet bool, match bool, err error) {
73+
if !val.IsSet() {
74+
return false, false, nil
75+
} else if val.IsWildcard() {
76+
return true, true, nil
77+
}
78+
quoted_etags := strings.Split(string(val), ", ")
79+
for _, quoted_etag := range quoted_etags {
80+
var e internal.ETag
81+
if err := e.UnmarshalText([]byte(quoted_etag)); err != nil {
82+
// opinionated returning `false` on match so caller
83+
// should definitely check for non-nil `err`
84+
return true, false, err
85+
} else if string(e) == etag {
86+
return true, true, nil
87+
}
6088
}
61-
t, err := val.ETag()
62-
return t == etag, err
89+
return true, false, nil
6390
}

webdav_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package webdav
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestConditionalMatch(t *testing.T) {
8+
// testing match and no match
9+
val := ConditionalMatch("\"AAA\", \"BBB\", \"CCC\"")
10+
if isSet, ok, err := val.MatchETag("AAA"); err != nil {
11+
t.Fatal(err)
12+
} else if !isSet {
13+
t.Fatalf("Expected isSet true")
14+
} else if !ok {
15+
t.Fatalf("Expected ok true")
16+
}
17+
if isSet, ok, err := val.MatchETag("DDD"); err != nil {
18+
t.Fatal(err)
19+
} else if !isSet {
20+
t.Fatalf("Expected isSet true")
21+
} else if ok {
22+
t.Fatalf("Expected ok false")
23+
}
24+
25+
// testing parse error
26+
val = ConditionalMatch("\"AAA\", BBB, CCC")
27+
if _, _, err := val.MatchETag("BBB"); err == nil {
28+
t.Fatalf("Expected non-nil error")
29+
}
30+
31+
// testing WildCard
32+
val = ConditionalMatch("*")
33+
if isSet, ok, err := val.MatchETag("BBB"); err != nil {
34+
t.Fatal(err)
35+
} else if !isSet {
36+
t.Fatalf("Expected isSet true")
37+
} else if !ok {
38+
t.Fatalf("Expected ok true")
39+
}
40+
41+
// testing isSet
42+
val = ConditionalMatch("")
43+
if isSet, _, _ := val.MatchETag("BBB"); isSet {
44+
t.Fatalf("Expected isSet false")
45+
}
46+
}

0 commit comments

Comments
 (0)