From 2a7d999100fcc68b9544884734cfbe03a4f733f6 Mon Sep 17 00:00:00 2001 From: emersion Date: Wed, 13 Sep 2017 19:02:12 +0200 Subject: [PATCH] carddav: PUT support --- carddav/backend.go | 4 +- carddav/fs.go | 135 ++++++++++++++++++++++++++++++++++----------- webdav.go | 15 +++-- 3 files changed, 118 insertions(+), 36 deletions(-) diff --git a/carddav/backend.go b/carddav/backend.go index 803e1f6..0ec561d 100644 --- a/carddav/backend.go +++ b/carddav/backend.go @@ -21,12 +21,14 @@ type AddressBookInfo struct { type AddressObject interface { ID() string - Card() (vcard.Card, error) Stat() (os.FileInfo, error) // can return nil, nil + Card() (vcard.Card, error) + SetCard(vcard.Card) error } type AddressBook interface { Info() (*AddressBookInfo, error) GetAddressObject(id string) (AddressObject, error) ListAddressObjects() ([]AddressObject, error) + CreateAddressObject(vcard.Card) (AddressObject, error) } diff --git a/carddav/fs.go b/carddav/fs.go index 39e8972..9c7398e 100644 --- a/carddav/fs.go +++ b/carddav/fs.go @@ -38,6 +38,10 @@ var ( addressBookHomeSet = xml.Name{Space: nsCardDAV, Local: "addressbook-home-set"} ) +func addressObjectName(ao AddressObject) string { + return ao.ID() + ".vcf" +} + type fileInfo struct { name string size int64 @@ -45,6 +49,13 @@ type fileInfo struct { modTime time.Time } +func addressObjectFileInfo(ao AddressObject) *fileInfo { + return &fileInfo{ + name: addressObjectName(ao), + mode: os.ModePerm, + } +} + func (fi *fileInfo) Name() string { return fi.name } @@ -70,18 +81,34 @@ func (fi *fileInfo) Sys() interface{} { } type file struct { - *bytes.Reader + r *bytes.Reader + w *bytes.Buffer fs *fileSystem - name string ao AddressObject } func (f *file) Close() error { + if f.w != nil { + defer func() { + f.w = nil + }() + + card, err := vcard.NewDecoder(f.w).Decode() + if err != nil { + return err + } + + if err := f.ao.SetCard(card); err != nil { + return err + } + } + + f.r = nil return nil } func (f *file) Read(b []byte) (int, error) { - if f.Reader == nil { + if f.r == nil { card, err := f.ao.Card() if err != nil { return 0, err @@ -92,24 +119,27 @@ func (f *file) Read(b []byte) (int, error) { return 0, err } - f.Reader = bytes.NewReader(b.Bytes()) + f.r = bytes.NewReader(b.Bytes()) } - return f.Reader.Read(b) + return f.r.Read(b) } func (f *file) Write(b []byte) (int, error) { - return 0, errUnsupported + if f.w == nil { + f.w = &bytes.Buffer{} + } + return f.w.Write(b) } func (f *file) Seek(offset int64, whence int) (int64, error) { - if f.Reader == nil { + if f.r == nil { if _, err := f.Read(nil); err != nil { return 0, err } } - return f.Reader.Seek(offset, whence) + return f.r.Seek(offset, whence) } func (f *file) Readdir(count int) ([]os.FileInfo, error) { @@ -121,15 +151,71 @@ func (f *file) Stat() (os.FileInfo, error) { if info != nil || err != nil { return info, err } - - return &fileInfo{ - name: f.name, - mode: os.ModePerm, - }, nil + return addressObjectFileInfo(f.ao), nil } // TODO: getcontenttype for file +type newFile struct { + buf bytes.Buffer + fs *fileSystem + ao AddressObject +} + +func (f *newFile) Close() error { + if f.ao == nil { + defer f.buf.Reset() + + card, err := vcard.NewDecoder(&f.buf).Decode() + if err != nil { + return err + } + + ao, err := f.fs.ab.CreateAddressObject(card) + if err != nil { + return err + } + + f.ao = ao + } + + return nil +} + +func (f *newFile) Read(b []byte) (int, error) { + return 0, errUnsupported +} + +func (f *newFile) Write(b []byte) (int, error) { + // TODO: limit amount of data in f.buf + if f.ao != nil { + return 0, errUnsupported + } + + return f.buf.Write(b) +} + +func (f *newFile) Seek(offset int64, whence int) (int64, error) { + return 0, errUnsupported +} + +func (f *newFile) Readdir(count int) ([]os.FileInfo, error) { + return nil, errUnsupported +} + +func (f *newFile) Stat() (os.FileInfo, error) { + // Only available after a successful call to Close + if f.ao == nil { + return nil, errUnsupported + } + + info, err := f.ao.Stat() + if info != nil || err != nil { + return info, err + } + return addressObjectFileInfo(f.ao), nil +} + type dir struct { fs *fileSystem name string @@ -163,18 +249,7 @@ func (d *dir) Readdir(count int) ([]os.FileInfo, error) { d.files = make([]os.FileInfo, len(aos)) for i, ao := range aos { - f := &file{ - fs: d.fs, - name: ao.ID() + ".vcf", - ao: ao, - } - - info, err := f.Stat() - if err != nil { - return nil, err - } - - d.files[i] = info + d.files[i] = addressObjectFileInfo(ao) } } @@ -262,13 +337,14 @@ func (fs *fileSystem) OpenFile(ctx context.Context, name string, flag int, perm id := fs.addressObjectID(name) ao, err := fs.ab.GetAddressObject(id) - if err != nil { + if err == ErrNotFound && flag & os.O_CREATE != 0 { + return &newFile{fs: fs}, nil + } else if err != nil { return nil, err } return &file{ fs: fs, - name: name, ao: ao, }, nil } @@ -300,8 +376,5 @@ func (fs *fileSystem) Stat(ctx context.Context, name string) (os.FileInfo, error return info, err } - return &fileInfo{ - name: name, - mode: os.ModePerm, - }, nil + return addressObjectFileInfo(ao), nil } diff --git a/webdav.go b/webdav.go index a3d8ce3..e7cb953 100644 --- a/webdav.go +++ b/webdav.go @@ -282,24 +282,31 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int, if err != nil { return http.StatusNotFound, err } + _, copyErr := io.Copy(f, r.Body) - fi, statErr := f.Stat() closeErr := f.Close() + fi, statErr := f.Stat() // TODO(rost): Returning 405 Method Not Allowed might not be appropriate. if copyErr != nil { return http.StatusMethodNotAllowed, copyErr } - if statErr != nil { - return http.StatusMethodNotAllowed, statErr - } if closeErr != nil { return http.StatusMethodNotAllowed, closeErr } + if statErr != nil { + return http.StatusMethodNotAllowed, statErr + } + + if path.Base(reqPath) != fi.Name() { + w.Header().Set("Location", path.Join(path.Dir(reqPath), fi.Name())) + } + etag, err := findETag(ctx, h.FileSystem, h.LockSystem, reqPath, fi) if err != nil { return http.StatusInternalServerError, err } w.Header().Set("ETag", etag) + return http.StatusCreated, nil }