diff --git a/go.mod b/go.mod index 739a3c2..af7737d 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/emersion/go-imap v1.2.0 github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac github.com/emersion/go-vcard v0.0.0-20210521075357-3445b9171995 - github.com/emersion/go-webdav v0.3.2-0.20220916070414-6f22a649ac4c + github.com/emersion/go-webdav v0.3.2-0.20221122105813-ac9af45270fb github.com/go-chi/chi/v5 v5.0.7 github.com/msteinert/pam v1.0.0 ) @@ -16,5 +16,3 @@ require ( github.com/teambition/rrule-go v1.7.2 // indirect golang.org/x/text v0.3.7 // indirect ) - -replace github.com/emersion/go-webdav v0.3.2-0.20220916070414-6f22a649ac4c => github.com/bitfehler/go-webdav v0.3.2-0.20221031110527-fb914f2f6d18 diff --git a/go.sum b/go.sum index 7550a4a..d604f6a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/bitfehler/go-webdav v0.3.2-0.20221031110527-fb914f2f6d18 h1:OCxO7C4E58C6GIhXDL1zQjsN5gq/xh7LhKKwg4/g8Co= -github.com/bitfehler/go-webdav v0.3.2-0.20221031110527-fb914f2f6d18/go.mod h1:lkPYZO/vsDNV9GPyVMBBsAUZzzxINL97bEVFykApo58= github.com/emersion/go-ical v0.0.0-20220601085725-0864dccc089f h1:feGUUxxvOtWVOhTko8Cbmp33a+tU0IMZxMEmnkoAISQ= github.com/emersion/go-ical v0.0.0-20220601085725-0864dccc089f/go.mod h1:2MKFUgfNMULRxqZkadG1Vh44we3y5gJAtTBlVsx1BKQ= github.com/emersion/go-imap v1.2.0 h1:lyUQ3+EVM21/qbWE/4Ya5UG9r5+usDxlg4yfp3TgHFA= @@ -12,6 +10,8 @@ github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1: github.com/emersion/go-vcard v0.0.0-20191221110513-5f81fa0d3cc7/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM= github.com/emersion/go-vcard v0.0.0-20210521075357-3445b9171995 h1:DpVfmcoBs6o9VYcccNWbuKFQxuHCgt25/y4q9H8AUvc= github.com/emersion/go-vcard v0.0.0-20210521075357-3445b9171995/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM= +github.com/emersion/go-webdav v0.3.2-0.20221122105813-ac9af45270fb h1:hf7lieMeu22uJuQ+TzglBMQ77C8R8h4OnHeLVHGDRQE= +github.com/emersion/go-webdav v0.3.2-0.20221122105813-ac9af45270fb/go.mod h1:lkPYZO/vsDNV9GPyVMBBsAUZzzxINL97bEVFykApo58= github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/msteinert/pam v1.0.0 h1:4XoXKtMCH3+e6GIkW41uxm6B37eYqci/DH3gzSq7ocg= diff --git a/storage/filesystem.go b/storage/filesystem.go index 7da8ea7..0f82ef7 100644 --- a/storage/filesystem.go +++ b/storage/filesystem.go @@ -84,7 +84,7 @@ func ensureLocalDir(path string) error { // don't use this directly, use localCalDAVPath or localCardDAVPath instead. func (b *filesystemBackend) safeLocalPath(homeSetPath string, urlPath string) (string, error) { - localPath := filepath.Join(b.path, homeSetPath) + localPath := filepath.Join(b.path, homeSetPath, "default") if err := ensureLocalDir(localPath); err != nil { return "", err } @@ -96,10 +96,10 @@ func (b *filesystemBackend) safeLocalPath(homeSetPath string, urlPath string) (s // We are mapping to local filesystem path, so be conservative about what to accept // TODO this changes once multiple addess books are supported dir, file := path.Split(urlPath) - // only accept resources in prefix, no subdirs for now - if dir != homeSetPath { - if strings.HasPrefix(dir, homeSetPath+"/") { - err := fmt.Errorf("invalid request path: %s", urlPath) + // only accept resources in default calendar/adress book for now + if path.Clean(dir) != path.Join(homeSetPath, "default") { + if strings.HasPrefix(dir, homeSetPath) { + err := fmt.Errorf("invalid request path: %s (%s is not %s)", urlPath, dir, path.Join(homeSetPath, "default")) return "", webdav.NewHTTPError(400, err) } else { err := fmt.Errorf("Access to resource outside of home set: %s", urlPath) @@ -228,26 +228,27 @@ func createDefaultAddressBook(path, localPath string) error { func (b *filesystemBackend) AddressBook(ctx context.Context) (*carddav.AddressBook, error) { debug.Printf("filesystem.AddressBook()") - path, err := b.localCardDAVPath(ctx, "") + localPath, err := b.localCardDAVPath(ctx, "") if err != nil { return nil, err } - path = filepath.Join(path, "addressbook.json") + localPath = filepath.Join(localPath, "addressbook.json") - debug.Printf("loading addressbook from %s", path) + debug.Printf("loading addressbook from %s", localPath) - data, readErr := ioutil.ReadFile(path) + data, readErr := ioutil.ReadFile(localPath) if os.IsNotExist(readErr) { urlPath, err := b.AddressbookHomeSetPath(ctx) if err != nil { return nil, err } - debug.Printf("creating default addressbook (URL:path): %s:%s", urlPath, path) - err = createDefaultAddressBook(urlPath, path) + urlPath = path.Join(urlPath, "default") + "/" + debug.Printf("creating default addressbook (URL:path): %s:%s", urlPath, localPath) + err = createDefaultAddressBook(urlPath, localPath) if err != nil { return nil, err } - data, readErr = ioutil.ReadFile(path) + data, readErr = ioutil.ReadFile(localPath) } if readErr != nil { return nil, fmt.Errorf("error opening address book: %s", readErr.Error()) @@ -330,7 +331,7 @@ func (b *filesystemBackend) loadAllContacts(ctx context.Context, propFilter []st } obj := carddav.AddressObject{ - Path: path.Join(homeSetPath, filepath.Base(filename)), + Path: path.Join(homeSetPath, "default", filepath.Base(filename)), ModTime: info.ModTime(), ContentLength: info.Size(), ETag: etag, @@ -375,7 +376,7 @@ func (b *filesystemBackend) loadAllCalendars(ctx context.Context, propFilter []s } obj := caldav.CalendarObject{ - Path: path.Join(homeSetPath, filepath.Base(filename)), + Path: path.Join(homeSetPath, "default", filepath.Base(filename)), ModTime: info.ModTime(), ContentLength: info.Size(), ETag: etag, @@ -427,17 +428,25 @@ func (b *filesystemBackend) PutAddressObject(ctx context.Context, objPath string } flags := os.O_RDWR | os.O_CREATE | os.O_TRUNC - if opts.IfNoneMatch { + // TODO handle IfNoneMatch == ETag + if opts.IfNoneMatch.IsWildcard() { + // Make sure we're not overwriting an existing file flags |= os.O_EXCL - } else if opts.IfMatch == "*" { + } else if opts.IfMatch.IsWildcard() { + // Make sure we _are_ overwriting an existing file flags &= ^os.O_CREATE - } else if opts.IfMatch != "" { + } else if opts.IfMatch.IsSet() { + // Make sure we overwrite the _right_ file etag, err := etagForFile(localPath) if err != nil { return "", webdav.NewHTTPError(http.StatusPreconditionFailed, err) } - if opts.IfMatch != etag { - err = fmt.Errorf("If-Match does not match current ETag (%s/%s)", opts.IfMatch, etag) + want, err := opts.IfMatch.ETag() + if err != nil { + return "", webdav.NewHTTPError(http.StatusBadRequest, err) + } + if want != etag { + err = fmt.Errorf("If-Match does not match current ETag (%s/%s)", want, etag) return "", webdav.NewHTTPError(http.StatusPreconditionFailed, err) } } @@ -495,26 +504,27 @@ func createDefaultCalendar(path, localPath string) error { func (b *filesystemBackend) Calendar(ctx context.Context) (*caldav.Calendar, error) { debug.Printf("filesystem.Calendar()") - path, err := b.localCalDAVPath(ctx, "") + localPath, err := b.localCalDAVPath(ctx, "") if err != nil { return nil, err } - path = filepath.Join(path, "calendar.json") + localPath = filepath.Join(localPath, "calendar.json") - debug.Printf("loading calendar from %s", path) + debug.Printf("loading calendar from %s", localPath) - data, readErr := ioutil.ReadFile(path) + data, readErr := ioutil.ReadFile(localPath) if os.IsNotExist(readErr) { - homeSetPath, err := b.CalendarHomeSetPath(ctx) + urlPath, err := b.CalendarHomeSetPath(ctx) if err != nil { return nil, err } - debug.Printf("creating default calendar (URL:path): %s:%s", homeSetPath, path) - err = createDefaultCalendar(homeSetPath, path) + urlPath = path.Join(urlPath, "default") + "/" + debug.Printf("creating default calendar (URL:path): %s:%s", urlPath, localPath) + err = createDefaultCalendar(urlPath, localPath) if err != nil { return nil, err } - data, readErr = ioutil.ReadFile(path) + data, readErr = ioutil.ReadFile(localPath) } if readErr != nil { return nil, fmt.Errorf("error opening calendar: %s", readErr.Error()) @@ -617,17 +627,25 @@ func (b *filesystemBackend) PutCalendarObject(ctx context.Context, objPath strin } flags := os.O_RDWR | os.O_CREATE | os.O_TRUNC - if opts.IfNoneMatch { + // TODO handle IfNoneMatch == ETag + if opts.IfNoneMatch.IsWildcard() { + // Make sure we're not overwriting an existing file flags |= os.O_EXCL - } else if opts.IfMatch == "*" { + } else if opts.IfMatch.IsWildcard() { + // Make sure we _are_ overwriting an existing file flags &= ^os.O_CREATE - } else if opts.IfMatch != "" { + } else if opts.IfMatch.IsSet() { + // Make sure we overwrite the _right_ file etag, err := etagForFile(localPath) if err != nil { return "", webdav.NewHTTPError(http.StatusPreconditionFailed, err) } - if opts.IfMatch != etag { - err = fmt.Errorf("If-Match does not match current ETag (%s/%s)", opts.IfMatch, etag) + want, err := opts.IfMatch.ETag() + if err != nil { + return "", webdav.NewHTTPError(http.StatusBadRequest, err) + } + if want != etag { + err = fmt.Errorf("If-Match does not match current ETag (%s/%s)", want, etag) return "", webdav.NewHTTPError(http.StatusPreconditionFailed, err) } }