From 9d778f4072038db1054a4a359f5230148b62724f Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 9 Dec 2024 22:31:59 +0100 Subject: [PATCH] webdav: add support for If-Match/If-None-Match in FileSystem.Create --- fs_local.go | 26 +++++++++++++++++++++----- server.go | 11 +++++++++-- webdav.go | 13 +++++++++++++ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/fs_local.go b/fs_local.go index 65df148..c2cfd46 100644 --- a/fs_local.go +++ b/fs_local.go @@ -114,15 +114,31 @@ func (fs LocalFileSystem) ReadDir(ctx context.Context, name string, recursive bo return l, errFromOS(err) } -func (fs LocalFileSystem) Create(ctx context.Context, name string, body io.ReadCloser) (*FileInfo, bool, error) { +func (fs LocalFileSystem) Create(ctx context.Context, name string, body io.ReadCloser, opts *CreateOptions) (fi *FileInfo, created bool, err error) { p, err := fs.localPath(name) if err != nil { return nil, false, err } - created := false - fi, _ := fs.Stat(ctx, name) - if fi == nil { - created = true + fi, _ = fs.Stat(ctx, name) + created = fi == nil + etag := "" + if fi != nil { + 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 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")) + } } wc, err := os.Create(p) diff --git a/server.go b/server.go index 3f7ea13..b5ba8f4 100644 --- a/server.go +++ b/server.go @@ -17,7 +17,7 @@ type FileSystem interface { Open(ctx context.Context, name string) (io.ReadCloser, error) Stat(ctx context.Context, name string) (*FileInfo, error) ReadDir(ctx context.Context, name string, recursive bool) ([]FileInfo, error) - Create(ctx context.Context, name string, body io.ReadCloser) (fileInfo *FileInfo, created bool, err error) + Create(ctx context.Context, name string, body io.ReadCloser, opts *CreateOptions) (fileInfo *FileInfo, created bool, err error) RemoveAll(ctx context.Context, name string) error Mkdir(ctx context.Context, name string) error Copy(ctx context.Context, name, dest string, options *CopyOptions) (created bool, err error) @@ -194,7 +194,14 @@ func (b *backend) PropPatch(r *http.Request, update *internal.PropertyUpdate) (* } func (b *backend) Put(w http.ResponseWriter, r *http.Request) error { - fi, created, err := b.FileSystem.Create(r.Context(), r.URL.Path, r.Body) + ifNoneMatch := ConditionalMatch(r.Header.Get("If-None-Match")) + ifMatch := ConditionalMatch(r.Header.Get("If-Match")) + + opts := CreateOptions{ + IfNoneMatch: ifNoneMatch, + IfMatch: ifMatch, + } + fi, created, err := b.FileSystem.Create(r.Context(), r.URL.Path, r.Body, &opts) if err != nil { return err } diff --git a/webdav.go b/webdav.go index e691ac5..eb5c742 100644 --- a/webdav.go +++ b/webdav.go @@ -19,6 +19,11 @@ type FileInfo struct { ETag string } +type CreateOptions struct { + IfMatch ConditionalMatch + IfNoneMatch ConditionalMatch +} + type CopyOptions struct { NoRecursive bool NoOverwrite bool @@ -48,3 +53,11 @@ func (val ConditionalMatch) ETag() (string, error) { } return string(e), nil } + +func (val ConditionalMatch) MatchETag(etag string) (bool, error) { + if val.IsWildcard() { + return true, nil + } + t, err := val.ETag() + return t == etag, err +}