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)