Skip to content
Open
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
25 changes: 13 additions & 12 deletions fs_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,19 +126,20 @@ func (fs LocalFileSystem) Create(ctx context.Context, name string, body io.ReadC
etag = fi.ETag
}

if opts.IfMatch.IsSet() {
if ok, err := opts.IfMatch.MatchETag(etag); err != nil {
return nil, false, NewHTTPError(http.StatusBadRequest, err)
} else if !ok {
return nil, false, NewHTTPError(http.StatusPreconditionFailed, fmt.Errorf("If-Match condition failed"))
}
if isSet, ok, err := opts.IfMatch.MatchETag(etag); !isSet {
// not set so continue
} else if err != nil {
return nil, false, NewHTTPError(http.StatusBadRequest, err)
} else if isSet && !ok {
return nil, false, NewHTTPError(http.StatusPreconditionFailed, fmt.Errorf("If-Match condition failed"))
}
if opts.IfNoneMatch.IsSet() {
if ok, err := opts.IfNoneMatch.MatchETag(etag); err != nil {
return nil, false, NewHTTPError(http.StatusBadRequest, err)
} else if ok {
return nil, false, NewHTTPError(http.StatusPreconditionFailed, fmt.Errorf("If-None-Match condition failed"))
}

if isSet, ok, err := opts.IfNoneMatch.MatchETag(etag); !isSet {
// not set so continue
} else if err != nil {
return nil, false, NewHTTPError(http.StatusBadRequest, err)
} else if isSet && ok {
return nil, false, NewHTTPError(http.StatusPreconditionFailed, fmt.Errorf("If-None-Match condition failed"))
}

wc, err := os.Create(p)
Expand Down
37 changes: 32 additions & 5 deletions webdav.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package webdav

import (
"strings"
"time"

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

func (val ConditionalMatch) MatchETag(etag string) (bool, error) {
if val.IsWildcard() {
return true, nil
// MatchETag checks if the ETag provided matches any of the ETags in the ConditionalMatch header value.
//
// Parameters:
// - etag: The ETag to match against.
//
// Returns:
// - isSet: Indicates if the ConditionalMatch has any ETags set, or is wildcard.
// - match: True if the etag matches any of the ETags in ConditionalMatch, false otherwise.
// - err: An error if there was a problem parsing one of the ETags.
// If an error occurs during parsing, match will be set to false, but isSet will be true.
// Callers should check for a non-nil error to ensure the match result is valid.
//
// The function returns early if no ETags are set (isSet is false) or if a wildcard (*) is used,
// in which case all ETags match. For multiple ETags, it checks each one until a match is found or all are checked.
func (val ConditionalMatch) MatchETag(etag string) (isSet bool, match bool, err error) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rationale for returning isSet

the MatchETag function should check whether val.IsSet() is non-nil, otherwise it leads to the wrong behaviour.

this seemed the only reasonable solution (beyond implementors inspecting error types), and reduces boilerplate code for implementations.

if !val.IsSet() {
return false, false, nil
} else if val.IsWildcard() {
return true, true, nil
}
quoted_etags := strings.Split(string(val), ", ")
for _, quoted_etag := range quoted_etags {
var e internal.ETag
if err := e.UnmarshalText([]byte(quoted_etag)); err != nil {
// opinionated returning `false` on match so caller
// should definitely check for non-nil `err`
return true, false, err
} else if string(e) == etag {
return true, true, nil
}
}
t, err := val.ETag()
return t == etag, err
return true, false, nil
}
46 changes: 46 additions & 0 deletions webdav_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package webdav

import (
"testing"
)

func TestConditionalMatch(t *testing.T) {
// testing match and no match
val := ConditionalMatch("\"AAA\", \"BBB\", \"CCC\"")
if isSet, ok, err := val.MatchETag("AAA"); err != nil {
t.Fatal(err)
} else if !isSet {
t.Fatalf("Expected isSet true")
} else if !ok {
t.Fatalf("Expected ok true")
}
if isSet, ok, err := val.MatchETag("DDD"); err != nil {
t.Fatal(err)
} else if !isSet {
t.Fatalf("Expected isSet true")
} else if ok {
t.Fatalf("Expected ok false")
}

// testing parse error
val = ConditionalMatch("\"AAA\", BBB, CCC")
if _, _, err := val.MatchETag("BBB"); err == nil {
t.Fatalf("Expected non-nil error")
}

// testing WildCard
val = ConditionalMatch("*")
if isSet, ok, err := val.MatchETag("BBB"); err != nil {
t.Fatal(err)
} else if !isSet {
t.Fatalf("Expected isSet true")
} else if !ok {
t.Fatalf("Expected ok true")
}

// testing isSet
val = ConditionalMatch("")
if isSet, _, _ := val.MatchETag("BBB"); isSet {
t.Fatalf("Expected isSet false")
}
}