From 5d4f6342b0467f924ef4cd4190c82dc570a49d7e Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 16 Dec 2024 19:13:30 +0100 Subject: [PATCH] webdav: add support for If-Match/If-None-Match in DELETE --- fs_local.go | 49 ++++++++++++++++++++++++++++++++----------------- server.go | 11 +++++++++-- webdav.go | 5 +++++ 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/fs_local.go b/fs_local.go index c2cfd46..ed66dd0 100644 --- a/fs_local.go +++ b/fs_local.go @@ -114,33 +114,43 @@ 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, opts *CreateOptions) (fi *FileInfo, created bool, err error) { - p, err := fs.localPath(name) - if err != nil { - return nil, false, err - } - fi, _ = fs.Stat(ctx, name) - created = fi == nil +func checkConditionalMatches(fi *FileInfo, ifMatch, ifNoneMatch ConditionalMatch) error { 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) + if ifMatch.IsSet() { + if ok, err := ifMatch.MatchETag(etag); err != nil { + return NewHTTPError(http.StatusBadRequest, err) } else if !ok { - return nil, false, NewHTTPError(http.StatusPreconditionFailed, fmt.Errorf("If-Match condition failed")) + return 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) + + if ifNoneMatch.IsSet() { + if ok, err := ifNoneMatch.MatchETag(etag); err != nil { + return NewHTTPError(http.StatusBadRequest, err) } else if ok { - return nil, false, NewHTTPError(http.StatusPreconditionFailed, fmt.Errorf("If-None-Match condition failed")) + return NewHTTPError(http.StatusPreconditionFailed, fmt.Errorf("If-None-Match condition failed")) } } + return nil +} + +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 + } + fi, _ = fs.Stat(ctx, name) + created = fi == nil + + if err := checkConditionalMatches(fi, opts.IfMatch, opts.IfNoneMatch); err != nil { + return nil, false, err + } + wc, err := os.Create(p) if err != nil { return nil, false, errFromOS(err) @@ -164,7 +174,7 @@ func (fs LocalFileSystem) Create(ctx context.Context, name string, body io.ReadC return fi, created, err } -func (fs LocalFileSystem) RemoveAll(ctx context.Context, name string) error { +func (fs LocalFileSystem) RemoveAll(ctx context.Context, name string, opts *RemoveAllOptions) error { p, err := fs.localPath(name) if err != nil { return err @@ -172,10 +182,15 @@ func (fs LocalFileSystem) RemoveAll(ctx context.Context, name string) error { // WebDAV semantics are that it should return a "404 Not Found" error in // case the resource doesn't exist. We need to Stat before RemoveAll. - if _, err = os.Stat(p); err != nil { + fi, err := fs.Stat(ctx, name) + if err != nil { return errFromOS(err) } + if err := checkConditionalMatches(fi, opts.IfMatch, opts.IfNoneMatch); err != nil { + return err + } + return errFromOS(os.RemoveAll(p)) } diff --git a/server.go b/server.go index b5ba8f4..3d73209 100644 --- a/server.go +++ b/server.go @@ -18,7 +18,7 @@ type FileSystem interface { 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, opts *CreateOptions) (fileInfo *FileInfo, created bool, err error) - RemoveAll(ctx context.Context, name string) error + RemoveAll(ctx context.Context, name string, opts *RemoveAllOptions) error Mkdir(ctx context.Context, name string) error Copy(ctx context.Context, name, dest string, options *CopyOptions) (created bool, err error) Move(ctx context.Context, name, dest string, options *MoveOptions) (created bool, err error) @@ -226,7 +226,14 @@ func (b *backend) Put(w http.ResponseWriter, r *http.Request) error { } func (b *backend) Delete(r *http.Request) error { - return b.FileSystem.RemoveAll(r.Context(), r.URL.Path) + ifNoneMatch := ConditionalMatch(r.Header.Get("If-None-Match")) + ifMatch := ConditionalMatch(r.Header.Get("If-Match")) + + opts := RemoveAllOptions{ + IfNoneMatch: ifNoneMatch, + IfMatch: ifMatch, + } + return b.FileSystem.RemoveAll(r.Context(), r.URL.Path, &opts) } func (b *backend) Mkcol(r *http.Request) error { diff --git a/webdav.go b/webdav.go index eb5c742..ca16d4c 100644 --- a/webdav.go +++ b/webdav.go @@ -24,6 +24,11 @@ type CreateOptions struct { IfNoneMatch ConditionalMatch } +type RemoveAllOptions struct { + IfMatch ConditionalMatch + IfNoneMatch ConditionalMatch +} + type CopyOptions struct { NoRecursive bool NoOverwrite bool