diff --git a/carddav/carddav.go b/carddav/carddav.go
index af20174..abdf804 100644
--- a/carddav/carddav.go
+++ b/carddav/carddav.go
@@ -5,12 +5,9 @@ package carddav
import (
"bytes"
"encoding/xml"
- "errors"
"net/http"
+ "net/url"
"os"
- "strconv"
- "strings"
- "time"
"github.com/emersion/go-vcard"
"github.com/emersion/go-webdav"
@@ -19,310 +16,7 @@ import (
"log"
)
-var (
- errNotYetImplemented = errors.New("not yet implemented")
- errUnsupported = errors.New("unsupported")
-)
-
-const nsDAV = "DAV:"
-
-var (
- resourcetype = xml.Name{Space: nsDAV, Local: "resourcetype"}
- displayname = xml.Name{Space: nsDAV, Local: "displayname"}
- getcontenttype = xml.Name{Space: nsDAV, Local: "getcontenttype"}
-)
-
-const nsCardDAV = "urn:ietf:params:xml:ns:carddav"
-
-var (
- addressBookDescription = xml.Name{Space: nsCardDAV, Local: "addressbook-description"}
- addressBookSupportedAddressData = xml.Name{Space: nsCardDAV, Local: "supported-address-data"}
- addressBookMaxResourceSize = xml.Name{Space: nsCardDAV, Local: "max-resource-size"}
- addressBookHomeSet = xml.Name{Space: nsCardDAV, Local: "addressbook-home-set"}
-)
-
-type fileInfo struct {
- name string
- size int64
- mode os.FileMode
- modTime time.Time
-}
-
-func (fi *fileInfo) Name() string {
- return fi.name
-}
-
-func (fi *fileInfo) Size() int64 {
- return fi.size
-}
-
-func (fi *fileInfo) Mode() os.FileMode {
- return fi.mode
-}
-
-func (fi *fileInfo) ModTime() time.Time {
- return fi.modTime
-}
-
-func (fi *fileInfo) IsDir() bool {
- return fi.mode.IsDir()
-}
-
-func (fi *fileInfo) Sys() interface{} {
- return nil
-}
-
-type file struct {
- *bytes.Reader
- fs *fileSystem
- name string
- ao AddressObject
-}
-
-func (f *file) Close() error {
- return nil
-}
-
-func (f *file) Read(b []byte) (int, error) {
- if f.Reader == nil {
- card, err := f.ao.Card()
- if err != nil {
- return 0, err
- }
-
- var b bytes.Buffer
- if err := vcard.NewEncoder(&b).Encode(card); err != nil {
- return 0, err
- }
-
- f.Reader = bytes.NewReader(b.Bytes())
- }
-
- return f.Reader.Read(b)
-}
-
-func (f *file) Write(b []byte) (int, error) {
- return 0, errUnsupported
-}
-
-func (f *file) Seek(offset int64, whence int) (int64, error) {
- if f.Reader == nil {
- if _, err := f.Read(nil); err != nil {
- return 0, err
- }
- }
-
- return f.Reader.Seek(offset, whence)
-}
-
-func (f *file) Readdir(count int) ([]os.FileInfo, error) {
- return nil, errUnsupported
-}
-
-func (f *file) Stat() (os.FileInfo, error) {
- info, err := f.ao.Stat()
- if info != nil || err != nil {
- return info, err
- }
-
- return &fileInfo{
- name: f.name,
- mode: os.ModePerm,
- }, nil
-}
-
-// TODO: getcontenttype for file
-
-type dir struct {
- fs *fileSystem
- name string
- files []os.FileInfo
-
- n int
-}
-
-func (d *dir) Close() error {
- return nil
-}
-
-func (d *dir) Read(b []byte) (int, error) {
- return 0, errUnsupported
-}
-
-func (d *dir) Write(b []byte) (int, error) {
- return 0, errUnsupported
-}
-
-func (d *dir) Seek(offset int64, whence int) (int64, error) {
- return 0, errUnsupported
-}
-
-func (d *dir) Readdir(count int) ([]os.FileInfo, error) {
- if d.files == nil {
- aos, err := d.fs.ab.ListAddressObjects()
- if err != nil {
- return nil, err
- }
-
- 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
- }
- }
-
- if count == 0 {
- count = len(d.files) - d.n
- }
- if d.n >= len(d.files) {
- return nil, nil
- }
-
- from := d.n
- d.n += count
- if d.n > len(d.files) {
- d.n = len(d.files)
- }
-
- return d.files[from:d.n], nil
-}
-
-func (d *dir) Stat() (os.FileInfo, error) {
- return &fileInfo{
- name: d.name,
- mode: os.ModeDir | os.ModePerm,
- }, nil
-}
-
-func (d *dir) DeadProps() (map[xml.Name]webdav.Property, error) {
- info, err := d.fs.ab.Info()
- if err != nil {
- return nil, err
- }
-
- return map[xml.Name]webdav.Property{
- resourcetype: webdav.Property{
- XMLName: resourcetype,
- InnerXML: []byte(``),
- },
- displayname: webdav.Property{
- XMLName: displayname,
- InnerXML: []byte(info.Name),
- },
- addressBookDescription: webdav.Property{
- XMLName: addressBookDescription,
- InnerXML: []byte(info.Description),
- },
- addressBookSupportedAddressData: webdav.Property{
- XMLName: addressBookSupportedAddressData,
- InnerXML: []byte(`` +
- ``),
- },
- addressBookMaxResourceSize: webdav.Property{
- XMLName: addressBookMaxResourceSize,
- InnerXML: []byte(strconv.Itoa(info.MaxResourceSize)),
- },
- addressBookHomeSet: webdav.Property{
- XMLName: addressBookHomeSet,
- InnerXML: []byte(`/`),
- },
- }, nil
-}
-
-func (d *dir) Patch([]webdav.Proppatch) ([]webdav.Propstat, error) {
- return nil, errUnsupported
-}
-
-type fileSystem struct {
- ab AddressBook
-}
-
-func (fs *fileSystem) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
- return errNotYetImplemented
-}
-
-func (fs *fileSystem) addressObjectID(name string) string {
- return strings.TrimRight(strings.TrimLeft(name, "/"), ".vcf")
-}
-
-func (fs *fileSystem) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
- if name == "/" {
- return &dir{
- fs: fs,
- name: name,
- }, nil
- }
-
- id := fs.addressObjectID(name)
- ao, err := fs.ab.GetAddressObject(id)
- if err != nil {
- return nil, err
- }
-
- return &file{
- fs: fs,
- name: name,
- ao: ao,
- }, nil
-}
-
-func (fs *fileSystem) RemoveAll(ctx context.Context, name string) error {
- return errNotYetImplemented
-}
-
-func (fs *fileSystem) Rename(ctx context.Context, oldName, newName string) error {
- return errNotYetImplemented
-}
-
-func (fs *fileSystem) Stat(ctx context.Context, name string) (os.FileInfo, error) {
- if name == "/" {
- return &fileInfo{
- name: name,
- mode: os.ModeDir | os.ModePerm,
- }, nil
- }
-
- id := fs.addressObjectID(name)
- ao, err := fs.ab.GetAddressObject(id)
- if err != nil {
- return nil, err
- }
-
- info, err := ao.Stat()
- if info != nil || err != nil {
- return info, err
- }
-
- return &fileInfo{
- name: name,
- mode: os.ModePerm,
- }, nil
-}
-
-type Handler struct {
- webdav *webdav.Handler
-}
-
-func NewHandler(ab AddressBook) *Handler {
- return &Handler{&webdav.Handler{
- FileSystem: &fileSystem{ab},
- Logger: func(req *http.Request, err error) {
- if err != nil {
- log.Println("ERROR", req, err)
- }
- },
- }}
-}
+var addressDataName = xml.Name{Space: "urn:ietf:params:xml:ns:carddav", Local: "address-data"}
type responseWriter struct {
http.ResponseWriter
@@ -333,11 +27,162 @@ func (w responseWriter) Write(b []byte) (int, error) {
return w.ResponseWriter.Write(b)
}
-func (h *Handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
- log.Printf("%+v\n", req)
- if req.Method == http.MethodOptions {
- resp.Header().Add("DAV", "addressbook")
- }
- //h.webdav.ServeHTTP(resp, req)
- h.webdav.ServeHTTP(responseWriter{resp}, req)
+type Handler struct {
+ ab AddressBook
+ webdav *webdav.Handler
+}
+
+func NewHandler(ab AddressBook) *Handler {
+ return &Handler{
+ ab: ab,
+ webdav: &webdav.Handler{
+ FileSystem: &fileSystem{ab},
+ Logger: func(req *http.Request, err error) {
+ if err != nil {
+ log.Println("ERROR", req, err)
+ }
+ },
+ },
+ }
+}
+
+func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ log.Printf("%+v\n", r)
+ if r.Method == http.MethodOptions {
+ w.Header().Add("DAV", "addressbook")
+ }
+
+ w = responseWriter{w}
+ switch r.Method {
+ case "REPORT":
+ status, _ := h.handleReport(w, r)
+ if status != 0 {
+ w.WriteHeader(status)
+ }
+ case "OPTIONS":
+ w.Header().Add("Allow", "REPORT")
+ fallthrough
+ default:
+ h.webdav.ServeHTTP(w, r)
+ }
+}
+
+func (h *Handler) handleReport(w http.ResponseWriter, r *http.Request) (int, error) {
+ var mg addressbookMultiget
+ if err := xml.NewDecoder(r.Body).Decode(&mg); err != nil {
+ return http.StatusBadRequest, err
+ }
+ log.Printf("%#v\n", mg)
+
+ mw := webdav.NewMultistatusWriter(w)
+ defer mw.Close()
+
+ if len(mg.Href) == 0 {
+ mg.Href = []string{r.URL.Path}
+ }
+ for _, href := range mg.Href {
+ pstats, status, _ := multiget(r.Context(), h.webdav.FileSystem, h.webdav.LockSystem, href, []xml.Name(mg.Prop), mg.Allprop != nil)
+ // TODO: error handling
+
+ resp := &webdav.Response{
+ Href: []string{(&url.URL{Path: href}).EscapedPath()},
+ Status: status,
+ Propstat: pstats,
+ }
+
+ if err := mw.Write(resp); err != nil {
+ return http.StatusInternalServerError, err
+ }
+ }
+
+ return 0, nil
+}
+
+func multiget(ctx context.Context, fs webdav.FileSystem, ls webdav.LockSystem, name string, pnames []xml.Name, allprop bool) ([]webdav.Propstat, int, error) {
+ wantAddressData := false
+ for i, pname := range pnames {
+ if pname == addressDataName {
+ pnames = append(pnames[:i], pnames[i+1:]...)
+ wantAddressData = true
+ break
+ }
+ }
+
+ var pstats []webdav.Propstat
+ var err error
+ if allprop {
+ wantAddressData = true
+ pstats, err = webdav.Allprop(ctx, fs, ls, name, pnames)
+ } else {
+ pstats, err = webdav.Props(ctx, fs, ls, name, pnames)
+ }
+ if err != nil {
+ return pstats, http.StatusInternalServerError, err
+ }
+
+ // TODO: locking stuff
+
+ f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
+ if err != nil {
+ return nil, http.StatusNotFound, err
+ }
+ defer f.Close()
+ fi, err := f.Stat()
+ if err != nil {
+ return nil, http.StatusNotFound, err
+ }
+
+ if wantAddressData {
+ if fi.IsDir() {
+ // TODO
+ return nil, http.StatusNotFound, err
+ }
+
+ prop, status, _ := addressdata(f.(*file).ao)
+ if status == 0 {
+ status = http.StatusOK
+ }
+
+ inserted := false
+ for i, pstat := range pstats {
+ if pstat.Status == status {
+ pstats[i].Props = append(pstat.Props, prop)
+ inserted = true
+ break
+ }
+ }
+
+ if !inserted {
+ pstats = append(pstats, webdav.Propstat{
+ Props: []webdav.Property{prop},
+ Status: status,
+ })
+ }
+ }
+
+ return pstats, 0, nil
+}
+
+func addressdata(ao AddressObject) (webdav.Property, int, error) {
+ prop := webdav.Property{XMLName: addressDataName}
+
+ card, err := ao.Card()
+ if err != nil {
+ return prop, http.StatusInternalServerError, err
+ }
+
+ // TODO: restrict to requested props
+
+ var b bytes.Buffer
+ if err := vcard.NewEncoder(&b).Encode(card); err != nil {
+ return prop, http.StatusInternalServerError, err
+ }
+
+ var escaped bytes.Buffer
+ if err := xml.EscapeText(&escaped, b.Bytes()); err != nil {
+ return prop, http.StatusInternalServerError, err
+ }
+
+ prop.InnerXML = escaped.Bytes()
+ return prop, 0, nil
}
diff --git a/carddav/fs.go b/carddav/fs.go
new file mode 100644
index 0000000..add290c
--- /dev/null
+++ b/carddav/fs.go
@@ -0,0 +1,307 @@
+// Package carddav provides a CardDAV server implementation, as defined in
+// RFC 6352.
+package carddav
+
+import (
+ "bytes"
+ "encoding/xml"
+ "errors"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/emersion/go-vcard"
+ "github.com/emersion/go-webdav"
+ "golang.org/x/net/context"
+)
+
+var (
+ errNotYetImplemented = errors.New("not yet implemented")
+ errUnsupported = errors.New("unsupported")
+)
+
+const nsDAV = "DAV:"
+
+var (
+ resourcetype = xml.Name{Space: nsDAV, Local: "resourcetype"}
+ displayname = xml.Name{Space: nsDAV, Local: "displayname"}
+ getcontenttype = xml.Name{Space: nsDAV, Local: "getcontenttype"}
+)
+
+const nsCardDAV = "urn:ietf:params:xml:ns:carddav"
+
+var (
+ addressBookDescription = xml.Name{Space: nsCardDAV, Local: "addressbook-description"}
+ addressBookSupportedAddressData = xml.Name{Space: nsCardDAV, Local: "supported-address-data"}
+ addressBookMaxResourceSize = xml.Name{Space: nsCardDAV, Local: "max-resource-size"}
+ addressBookHomeSet = xml.Name{Space: nsCardDAV, Local: "addressbook-home-set"}
+)
+
+type fileInfo struct {
+ name string
+ size int64
+ mode os.FileMode
+ modTime time.Time
+}
+
+func (fi *fileInfo) Name() string {
+ return fi.name
+}
+
+func (fi *fileInfo) Size() int64 {
+ return fi.size
+}
+
+func (fi *fileInfo) Mode() os.FileMode {
+ return fi.mode
+}
+
+func (fi *fileInfo) ModTime() time.Time {
+ return fi.modTime
+}
+
+func (fi *fileInfo) IsDir() bool {
+ return fi.mode.IsDir()
+}
+
+func (fi *fileInfo) Sys() interface{} {
+ return nil
+}
+
+type file struct {
+ *bytes.Reader
+ fs *fileSystem
+ name string
+ ao AddressObject
+}
+
+func (f *file) Close() error {
+ return nil
+}
+
+func (f *file) Read(b []byte) (int, error) {
+ if f.Reader == nil {
+ card, err := f.ao.Card()
+ if err != nil {
+ return 0, err
+ }
+
+ var b bytes.Buffer
+ if err := vcard.NewEncoder(&b).Encode(card); err != nil {
+ return 0, err
+ }
+
+ f.Reader = bytes.NewReader(b.Bytes())
+ }
+
+ return f.Reader.Read(b)
+}
+
+func (f *file) Write(b []byte) (int, error) {
+ return 0, errUnsupported
+}
+
+func (f *file) Seek(offset int64, whence int) (int64, error) {
+ if f.Reader == nil {
+ if _, err := f.Read(nil); err != nil {
+ return 0, err
+ }
+ }
+
+ return f.Reader.Seek(offset, whence)
+}
+
+func (f *file) Readdir(count int) ([]os.FileInfo, error) {
+ return nil, errUnsupported
+}
+
+func (f *file) Stat() (os.FileInfo, error) {
+ info, err := f.ao.Stat()
+ if info != nil || err != nil {
+ return info, err
+ }
+
+ return &fileInfo{
+ name: f.name,
+ mode: os.ModePerm,
+ }, nil
+}
+
+// TODO: getcontenttype for file
+
+type dir struct {
+ fs *fileSystem
+ name string
+ files []os.FileInfo
+
+ n int
+}
+
+func (d *dir) Close() error {
+ return nil
+}
+
+func (d *dir) Read(b []byte) (int, error) {
+ return 0, errUnsupported
+}
+
+func (d *dir) Write(b []byte) (int, error) {
+ return 0, errUnsupported
+}
+
+func (d *dir) Seek(offset int64, whence int) (int64, error) {
+ return 0, errUnsupported
+}
+
+func (d *dir) Readdir(count int) ([]os.FileInfo, error) {
+ if d.files == nil {
+ aos, err := d.fs.ab.ListAddressObjects()
+ if err != nil {
+ return nil, err
+ }
+
+ 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
+ }
+ }
+
+ if count == 0 {
+ count = len(d.files) - d.n
+ }
+ if d.n >= len(d.files) {
+ return nil, nil
+ }
+
+ from := d.n
+ d.n += count
+ if d.n > len(d.files) {
+ d.n = len(d.files)
+ }
+
+ return d.files[from:d.n], nil
+}
+
+func (d *dir) Stat() (os.FileInfo, error) {
+ return &fileInfo{
+ name: d.name,
+ mode: os.ModeDir | os.ModePerm,
+ }, nil
+}
+
+func (d *dir) DeadProps() (map[xml.Name]webdav.Property, error) {
+ info, err := d.fs.ab.Info()
+ if err != nil {
+ return nil, err
+ }
+
+ return map[xml.Name]webdav.Property{
+ resourcetype: webdav.Property{
+ XMLName: resourcetype,
+ InnerXML: []byte(``),
+ },
+ displayname: webdav.Property{
+ XMLName: displayname,
+ InnerXML: []byte(info.Name),
+ },
+ addressBookDescription: webdav.Property{
+ XMLName: addressBookDescription,
+ InnerXML: []byte(info.Description),
+ },
+ addressBookSupportedAddressData: webdav.Property{
+ XMLName: addressBookSupportedAddressData,
+ InnerXML: []byte(`` +
+ ``),
+ },
+ addressBookMaxResourceSize: webdav.Property{
+ XMLName: addressBookMaxResourceSize,
+ InnerXML: []byte(strconv.Itoa(info.MaxResourceSize)),
+ },
+ addressBookHomeSet: webdav.Property{
+ XMLName: addressBookHomeSet,
+ InnerXML: []byte(`/`),
+ },
+ }, nil
+}
+
+func (d *dir) Patch([]webdav.Proppatch) ([]webdav.Propstat, error) {
+ return nil, errUnsupported
+}
+
+type fileSystem struct {
+ ab AddressBook
+}
+
+func (fs *fileSystem) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
+ return errNotYetImplemented
+}
+
+func (fs *fileSystem) addressObjectID(name string) string {
+ return strings.TrimRight(strings.TrimLeft(name, "/"), ".vcf")
+}
+
+func (fs *fileSystem) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
+ if name == "/" {
+ return &dir{
+ fs: fs,
+ name: name,
+ }, nil
+ }
+
+ id := fs.addressObjectID(name)
+ ao, err := fs.ab.GetAddressObject(id)
+ if err != nil {
+ return nil, err
+ }
+
+ return &file{
+ fs: fs,
+ name: name,
+ ao: ao,
+ }, nil
+}
+
+func (fs *fileSystem) RemoveAll(ctx context.Context, name string) error {
+ return errNotYetImplemented
+}
+
+func (fs *fileSystem) Rename(ctx context.Context, oldName, newName string) error {
+ return errNotYetImplemented
+}
+
+func (fs *fileSystem) Stat(ctx context.Context, name string) (os.FileInfo, error) {
+ if name == "/" {
+ return &fileInfo{
+ name: name,
+ mode: os.ModeDir | os.ModePerm,
+ }, nil
+ }
+
+ id := fs.addressObjectID(name)
+ ao, err := fs.ab.GetAddressObject(id)
+ if err != nil {
+ return nil, err
+ }
+
+ info, err := ao.Stat()
+ if info != nil || err != nil {
+ return info, err
+ }
+
+ return &fileInfo{
+ name: name,
+ mode: os.ModePerm,
+ }, nil
+}
diff --git a/carddav/xml.go b/carddav/xml.go
new file mode 100644
index 0000000..0367e8f
--- /dev/null
+++ b/carddav/xml.go
@@ -0,0 +1,24 @@
+package carddav
+
+import (
+ "encoding/xml"
+
+ "github.com/emersion/go-webdav"
+)
+
+// https://tools.ietf.org/html/rfc6352#section-10.7
+type addressbookMultiget struct {
+ XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav addressbook-multiget"`
+ Allprop *struct{} `xml:"DAV: allprop"`
+ Propname *struct{} `xml:"DAV: propname"`
+ Prop webdav.PropfindProps `xml:"DAV: prop"`
+ Href []string `xml:"DAV: href"`
+}
+
+// TODO
+type addressData struct {
+ XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav address-data"`
+ ContentType string `xml:"content-type,attr"`
+ Version string `xml:"version,attr"`
+ Prop []string `xml:"prop>name,attr"`
+}
diff --git a/prop.go b/prop.go
index e36a3b3..0ef0f67 100644
--- a/prop.go
+++ b/prop.go
@@ -160,13 +160,11 @@ var liveProps = map[xml.Name]struct {
},
}
-// TODO(nigeltao) merge props and allprop?
-
// Props returns the status of the properties named pnames for resource name.
//
// Each Propstat has a unique status and each property name will only be part
// of one Propstat element.
-func props(ctx context.Context, fs FileSystem, ls LockSystem, name string, pnames []xml.Name) ([]Propstat, error) {
+func Props(ctx context.Context, fs FileSystem, ls LockSystem, name string, pnames []xml.Name) ([]Propstat, error) {
f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
if err != nil {
return nil, err
@@ -254,7 +252,7 @@ func propnames(ctx context.Context, fs FileSystem, ls LockSystem, name string) (
// returned if they are named in 'include'.
//
// See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
-func allprop(ctx context.Context, fs FileSystem, ls LockSystem, name string, include []xml.Name) ([]Propstat, error) {
+func Allprop(ctx context.Context, fs FileSystem, ls LockSystem, name string, include []xml.Name) ([]Propstat, error) {
pnames, err := propnames(ctx, fs, ls, name)
if err != nil {
return nil, err
@@ -269,7 +267,7 @@ func allprop(ctx context.Context, fs FileSystem, ls LockSystem, name string, inc
pnames = append(pnames, pn)
}
}
- return props(ctx, fs, ls, name, pnames)
+ return Props(ctx, fs, ls, name, pnames)
}
// Patch patches the properties of resource name. The return values are
diff --git a/prop_test.go b/prop_test.go
index 57d0e82..3c155a5 100644
--- a/prop_test.go
+++ b/prop_test.go
@@ -534,9 +534,9 @@ func TestMemPS(t *testing.T) {
}
continue
case "allprop":
- propstats, err = allprop(ctx, fs, ls, op.name, op.pnames)
+ propstats, err = Allprop(ctx, fs, ls, op.name, op.pnames)
case "propfind":
- propstats, err = props(ctx, fs, ls, op.name, op.pnames)
+ propstats, err = Props(ctx, fs, ls, op.name, op.pnames)
case "proppatch":
propstats, err = patch(ctx, fs, ls, op.name, op.patches)
default:
diff --git a/webdav.go b/webdav.go
index c2dbf6d..a3d8ce3 100644
--- a/webdav.go
+++ b/webdav.go
@@ -577,9 +577,9 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
}
pstats = append(pstats, pstat)
} else if pf.Allprop != nil {
- pstats, err = allprop(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
+ pstats, err = Allprop(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
} else {
- pstats, err = props(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
+ pstats, err = Props(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
}
if err != nil {
return err
diff --git a/xml.go b/xml.go
index 4fb0b2b..43cda6d 100644
--- a/xml.go
+++ b/xml.go
@@ -115,13 +115,13 @@ func next(d *xml.Decoder) (xml.Token, error) {
}
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind)
-type propfindProps []xml.Name
+type PropfindProps []xml.Name
// UnmarshalXML appends the property names enclosed within start to pn.
//
// It returns an error if start does not contain any properties or if
// properties contain values. Character data between properties is ignored.
-func (pn *propfindProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+func (pn *PropfindProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
for {
t, err := next(d)
if err != nil {
@@ -152,8 +152,8 @@ type propfind struct {
XMLName xml.Name `xml:"DAV: propfind"`
Allprop *struct{} `xml:"DAV: allprop"`
Propname *struct{} `xml:"DAV: propname"`
- Prop propfindProps `xml:"DAV: prop"`
- Include propfindProps `xml:"DAV: include"`
+ Prop PropfindProps `xml:"DAV: prop"`
+ Include PropfindProps `xml:"DAV: include"`
}
func readPropfind(r io.Reader) (pf propfind, status int, err error) {
@@ -246,12 +246,16 @@ type MultistatusWriter struct {
// of the multistatus XML element. Only the latest content before
// close will be emitted. Empty response descriptions are not
// written.
- responseDescription string
+ ResponseDescription string
w http.ResponseWriter
enc *xml.Encoder
}
+func NewMultistatusWriter(w http.ResponseWriter) *MultistatusWriter {
+ return &MultistatusWriter{w: w}
+}
+
// Write validates and emits a DAV response as part of a multistatus response
// element.
//
@@ -337,11 +341,11 @@ func (w *MultistatusWriter) Close() error {
return nil
}
var end []xml.Token
- if w.responseDescription != "" {
+ if w.ResponseDescription != "" {
name := xml.Name{Space: "DAV:", Local: "responsedescription"}
end = append(end,
xml.StartElement{Name: name},
- xml.CharData(w.responseDescription),
+ xml.CharData(w.ResponseDescription),
xml.EndElement{Name: name},
)
}
diff --git a/xml_test.go b/xml_test.go
index 7fe5683..68b0d60 100644
--- a/xml_test.go
+++ b/xml_test.go
@@ -175,7 +175,7 @@ func TestReadPropfind(t *testing.T) {
wantPF: propfind{
XMLName: xml.Name{Space: "DAV:", Local: "propfind"},
Allprop: new(struct{}),
- Include: propfindProps{xml.Name{Space: "DAV:", Local: "displayname"}},
+ Include: PropfindProps{xml.Name{Space: "DAV:", Local: "displayname"}},
},
}, {
desc: "propfind: include followed by allprop",
@@ -187,7 +187,7 @@ func TestReadPropfind(t *testing.T) {
wantPF: propfind{
XMLName: xml.Name{Space: "DAV:", Local: "propfind"},
Allprop: new(struct{}),
- Include: propfindProps{xml.Name{Space: "DAV:", Local: "displayname"}},
+ Include: PropfindProps{xml.Name{Space: "DAV:", Local: "displayname"}},
},
}, {
desc: "propfind: propfind",
@@ -197,7 +197,7 @@ func TestReadPropfind(t *testing.T) {
"",
wantPF: propfind{
XMLName: xml.Name{Space: "DAV:", Local: "propfind"},
- Prop: propfindProps{xml.Name{Space: "DAV:", Local: "displayname"}},
+ Prop: PropfindProps{xml.Name{Space: "DAV:", Local: "displayname"}},
},
}, {
desc: "propfind: prop with ignored comments",
@@ -210,7 +210,7 @@ func TestReadPropfind(t *testing.T) {
"",
wantPF: propfind{
XMLName: xml.Name{Space: "DAV:", Local: "propfind"},
- Prop: propfindProps{xml.Name{Space: "DAV:", Local: "displayname"}},
+ Prop: PropfindProps{xml.Name{Space: "DAV:", Local: "displayname"}},
},
}, {
desc: "propfind: propfind with ignored whitespace",
@@ -220,7 +220,7 @@ func TestReadPropfind(t *testing.T) {
"",
wantPF: propfind{
XMLName: xml.Name{Space: "DAV:", Local: "propfind"},
- Prop: propfindProps{xml.Name{Space: "DAV:", Local: "displayname"}},
+ Prop: PropfindProps{xml.Name{Space: "DAV:", Local: "displayname"}},
},
}, {
desc: "propfind: propfind with ignored mixed-content",
@@ -230,7 +230,7 @@ func TestReadPropfind(t *testing.T) {
"",
wantPF: propfind{
XMLName: xml.Name{Space: "DAV:", Local: "propfind"},
- Prop: propfindProps{xml.Name{Space: "DAV:", Local: "displayname"}},
+ Prop: PropfindProps{xml.Name{Space: "DAV:", Local: "displayname"}},
},
}, {
desc: "propfind: propname with ignored element (section A.4)",
@@ -564,7 +564,7 @@ func TestMultistatusWriter(t *testing.T) {
loop:
for _, tc := range testCases {
rec := httptest.NewRecorder()
- w := MultistatusWriter{w: rec, responseDescription: tc.respdesc}
+ w := MultistatusWriter{w: rec, ResponseDescription: tc.respdesc}
if tc.writeHeader {
if err := w.writeHeader(); err != nil {
t.Errorf("%s: got writeHeader error %v, want nil", tc.desc, err)