diff --git a/caldav/server.go b/caldav/server.go index d3e83b8..9397c17 100644 --- a/caldav/server.go +++ b/caldav/server.go @@ -464,15 +464,15 @@ func (b *backend) propFindRoot(ctx context.Context, propfind *internal.PropFind) return nil, err } - props := map[xml.Name]internal.PropFindFunc{ - internal.CurrentUserPrincipalName: func(*internal.RawXMLValue) (interface{}, error) { + props := map[xml.Name]webdav.PropFindFunc{ + internal.CurrentUserPrincipalName: func() (interface{}, error) { return &internal.CurrentUserPrincipal{Href: internal.Href{Path: principalPath}}, nil }, - internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) { + internal.ResourceTypeName: func() (interface{}, error) { return internal.NewResourceType(internal.CollectionName), nil }, } - return internal.NewPropFindResponse(principalPath, propfind, props) + return webdav.NewPropFindResponse(principalPath, propfind, props) } func (b *backend) propFindUserPrincipal(ctx context.Context, propfind *internal.PropFind) (*internal.Response, error) { @@ -485,18 +485,18 @@ func (b *backend) propFindUserPrincipal(ctx context.Context, propfind *internal. return nil, err } - props := map[xml.Name]internal.PropFindFunc{ - internal.CurrentUserPrincipalName: func(*internal.RawXMLValue) (interface{}, error) { + props := map[xml.Name]webdav.PropFindFunc{ + internal.CurrentUserPrincipalName: func() (interface{}, error) { return &internal.CurrentUserPrincipal{Href: internal.Href{Path: principalPath}}, nil }, - calendarHomeSetName: func(*internal.RawXMLValue) (interface{}, error) { + calendarHomeSetName: func() (interface{}, error) { return &calendarHomeSet{Href: internal.Href{Path: homeSetPath}}, nil }, - internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) { + internal.ResourceTypeName: func() (interface{}, error) { return internal.NewResourceType(internal.CollectionName), nil }, } - return internal.NewPropFindResponse(principalPath, propfind, props) + return webdav.NewPropFindResponse(principalPath, propfind, props) } func (b *backend) propFindHomeSet(ctx context.Context, propfind *internal.PropFind) (*internal.Response, error) { @@ -510,40 +510,40 @@ func (b *backend) propFindHomeSet(ctx context.Context, propfind *internal.PropFi } // TODO anything else to return here? - props := map[xml.Name]internal.PropFindFunc{ - internal.CurrentUserPrincipalName: func(*internal.RawXMLValue) (interface{}, error) { + props := map[xml.Name]webdav.PropFindFunc{ + internal.CurrentUserPrincipalName: func() (interface{}, error) { return &internal.CurrentUserPrincipal{Href: internal.Href{Path: principalPath}}, nil }, - internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) { + internal.ResourceTypeName: func() (interface{}, error) { return internal.NewResourceType(internal.CollectionName), nil }, } - return internal.NewPropFindResponse(homeSetPath, propfind, props) + return webdav.NewPropFindResponse(homeSetPath, propfind, props) } func (b *backend) propFindCalendar(ctx context.Context, propfind *internal.PropFind, cal *Calendar) (*internal.Response, error) { - props := map[xml.Name]internal.PropFindFunc{ - internal.CurrentUserPrincipalName: func(*internal.RawXMLValue) (interface{}, error) { + props := map[xml.Name]webdav.PropFindFunc{ + internal.CurrentUserPrincipalName: func() (interface{}, error) { path, err := b.Backend.CurrentUserPrincipal(ctx) if err != nil { return nil, err } return &internal.CurrentUserPrincipal{Href: internal.Href{Path: path}}, nil }, - internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) { + internal.ResourceTypeName: func() (interface{}, error) { return internal.NewResourceType(internal.CollectionName, calendarName), nil }, - calendarDescriptionName: func(*internal.RawXMLValue) (interface{}, error) { + calendarDescriptionName: func() (interface{}, error) { return &calendarDescription{Description: cal.Description}, nil }, - supportedCalendarDataName: func(*internal.RawXMLValue) (interface{}, error) { + supportedCalendarDataName: func() (interface{}, error) { return &supportedCalendarData{ Types: []calendarDataType{ {ContentType: ical.MIMEType, Version: "2.0"}, }, }, nil }, - supportedCalendarComponentSetName: func(*internal.RawXMLValue) (interface{}, error) { + supportedCalendarComponentSetName: func() (interface{}, error) { components := []comp{} if cal.SupportedComponentSet != nil { for _, name := range cal.SupportedComponentSet { @@ -559,24 +559,23 @@ func (b *backend) propFindCalendar(ctx context.Context, propfind *internal.PropF } if cal.Name != "" { - props[internal.DisplayNameName] = func(*internal.RawXMLValue) (interface{}, error) { + props[internal.DisplayNameName] = func() (interface{}, error) { return &internal.DisplayName{Name: cal.Name}, nil } } if cal.Description != "" { - props[calendarDescriptionName] = func(*internal.RawXMLValue) (interface{}, error) { + props[calendarDescriptionName] = func() (interface{}, error) { return &calendarDescription{Description: cal.Description}, nil } } if cal.MaxResourceSize > 0 { - props[maxResourceSizeName] = func(*internal.RawXMLValue) (interface{}, error) { + props[maxResourceSizeName] = func() (interface{}, error) { return &maxResourceSize{Size: cal.MaxResourceSize}, nil } } // TODO: CALDAV:calendar-timezone, CALDAV:supported-calendar-component-set, CALDAV:min-date-time, CALDAV:max-date-time, CALDAV:max-instances, CALDAV:max-attendees-per-instance - - return internal.NewPropFindResponse(cal.Path, propfind, props) + return webdav.NewPropFindResponse(cal.Path, propfind, props) } func (b *backend) propFindAllCalendars(ctx context.Context, propfind *internal.PropFind, recurse bool) ([]internal.Response, error) { @@ -604,19 +603,19 @@ func (b *backend) propFindAllCalendars(ctx context.Context, propfind *internal.P } func (b *backend) propFindCalendarObject(ctx context.Context, propfind *internal.PropFind, co *CalendarObject) (*internal.Response, error) { - props := map[xml.Name]internal.PropFindFunc{ - internal.CurrentUserPrincipalName: func(*internal.RawXMLValue) (interface{}, error) { + props := map[xml.Name]webdav.PropFindFunc{ + internal.CurrentUserPrincipalName: func() (interface{}, error) { path, err := b.Backend.CurrentUserPrincipal(ctx) if err != nil { return nil, err } return &internal.CurrentUserPrincipal{Href: internal.Href{Path: path}}, nil }, - internal.GetContentTypeName: func(*internal.RawXMLValue) (interface{}, error) { + internal.GetContentTypeName: func() (interface{}, error) { return &internal.GetContentType{Type: ical.MIMEType}, nil }, // TODO: calendar-data can only be used in REPORT requests - calendarDataName: func(*internal.RawXMLValue) (interface{}, error) { + calendarDataName: func() (interface{}, error) { var buf bytes.Buffer if err := ical.NewEncoder(&buf).Encode(co.Data); err != nil { return nil, err @@ -627,23 +626,23 @@ func (b *backend) propFindCalendarObject(ctx context.Context, propfind *internal } if co.ContentLength > 0 { - props[internal.GetContentLengthName] = func(*internal.RawXMLValue) (interface{}, error) { + props[internal.GetContentLengthName] = func() (interface{}, error) { return &internal.GetContentLength{Length: co.ContentLength}, nil } } if !co.ModTime.IsZero() { - props[internal.GetLastModifiedName] = func(*internal.RawXMLValue) (interface{}, error) { + props[internal.GetLastModifiedName] = func() (interface{}, error) { return &internal.GetLastModified{LastModified: internal.Time(co.ModTime)}, nil } } if co.ETag != "" { - props[internal.GetETagName] = func(*internal.RawXMLValue) (interface{}, error) { + props[internal.GetETagName] = func() (interface{}, error) { return &internal.GetETag{ETag: internal.ETag(co.ETag)}, nil } } - return internal.NewPropFindResponse(co.Path, propfind, props) + return webdav.NewPropFindResponse(co.Path, propfind, props) } func (b *backend) propFindAllCalendarObjects(ctx context.Context, propfind *internal.PropFind, cal *Calendar) ([]internal.Response, error) { diff --git a/carddav/server.go b/carddav/server.go index 915c49e..fbdeac7 100644 --- a/carddav/server.go +++ b/carddav/server.go @@ -430,15 +430,15 @@ func (b *backend) propFindRoot(ctx context.Context, propfind *internal.PropFind) return nil, err } - props := map[xml.Name]internal.PropFindFunc{ - internal.CurrentUserPrincipalName: func(*internal.RawXMLValue) (interface{}, error) { + props := map[xml.Name]webdav.PropFindFunc{ + internal.CurrentUserPrincipalName: func() (interface{}, error) { return &internal.CurrentUserPrincipal{Href: internal.Href{Path: principalPath}}, nil }, - internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) { + internal.ResourceTypeName: func() (interface{}, error) { return internal.NewResourceType(internal.CollectionName), nil }, } - return internal.NewPropFindResponse(principalPath, propfind, props) + return webdav.NewPropFindResponse(principalPath, propfind, props) } func (b *backend) propFindUserPrincipal(ctx context.Context, propfind *internal.PropFind) (*internal.Response, error) { @@ -451,18 +451,18 @@ func (b *backend) propFindUserPrincipal(ctx context.Context, propfind *internal. return nil, err } - props := map[xml.Name]internal.PropFindFunc{ - internal.CurrentUserPrincipalName: func(*internal.RawXMLValue) (interface{}, error) { + props := map[xml.Name]webdav.PropFindFunc{ + internal.CurrentUserPrincipalName: func() (interface{}, error) { return &internal.CurrentUserPrincipal{Href: internal.Href{Path: principalPath}}, nil }, - addressBookHomeSetName: func(*internal.RawXMLValue) (interface{}, error) { + addressBookHomeSetName: func() (interface{}, error) { return &addressbookHomeSet{Href: internal.Href{Path: homeSetPath}}, nil }, - internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) { + internal.ResourceTypeName: func() (interface{}, error) { return internal.NewResourceType(internal.CollectionName), nil }, } - return internal.NewPropFindResponse(principalPath, propfind, props) + return webdav.NewPropFindResponse(principalPath, propfind, props) } func (b *backend) propFindHomeSet(ctx context.Context, propfind *internal.PropFind) (*internal.Response, error) { @@ -476,30 +476,30 @@ func (b *backend) propFindHomeSet(ctx context.Context, propfind *internal.PropFi } // TODO anything else to return here? - props := map[xml.Name]internal.PropFindFunc{ - internal.CurrentUserPrincipalName: func(*internal.RawXMLValue) (interface{}, error) { + props := map[xml.Name]webdav.PropFindFunc{ + internal.CurrentUserPrincipalName: func() (interface{}, error) { return &internal.CurrentUserPrincipal{Href: internal.Href{Path: principalPath}}, nil }, - internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) { + internal.ResourceTypeName: func() (interface{}, error) { return internal.NewResourceType(internal.CollectionName), nil }, } - return internal.NewPropFindResponse(homeSetPath, propfind, props) + return webdav.NewPropFindResponse(homeSetPath, propfind, props) } func (b *backend) propFindAddressBook(ctx context.Context, propfind *internal.PropFind, ab *AddressBook) (*internal.Response, error) { - props := map[xml.Name]internal.PropFindFunc{ - internal.CurrentUserPrincipalName: func(*internal.RawXMLValue) (interface{}, error) { + props := map[xml.Name]webdav.PropFindFunc{ + internal.CurrentUserPrincipalName: func() (interface{}, error) { path, err := b.Backend.CurrentUserPrincipal(ctx) if err != nil { return nil, err } return &internal.CurrentUserPrincipal{Href: internal.Href{Path: path}}, nil }, - internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) { + internal.ResourceTypeName: func() (interface{}, error) { return internal.NewResourceType(internal.CollectionName, addressBookName), nil }, - supportedAddressDataName: func(*internal.RawXMLValue) (interface{}, error) { + supportedAddressDataName: func() (interface{}, error) { return &supportedAddressData{ Types: []addressDataType{ {ContentType: vcard.MIMEType, Version: "3.0"}, @@ -510,22 +510,22 @@ func (b *backend) propFindAddressBook(ctx context.Context, propfind *internal.Pr } if ab.Name != "" { - props[internal.DisplayNameName] = func(*internal.RawXMLValue) (interface{}, error) { + props[internal.DisplayNameName] = func() (interface{}, error) { return &internal.DisplayName{Name: ab.Name}, nil } } if ab.Description != "" { - props[addressBookDescriptionName] = func(*internal.RawXMLValue) (interface{}, error) { + props[addressBookDescriptionName] = func() (interface{}, error) { return &addressbookDescription{Description: ab.Description}, nil } } if ab.MaxResourceSize > 0 { - props[maxResourceSizeName] = func(*internal.RawXMLValue) (interface{}, error) { + props[maxResourceSizeName] = func() (interface{}, error) { return &maxResourceSize{Size: ab.MaxResourceSize}, nil } } - return internal.NewPropFindResponse(ab.Path, propfind, props) + return webdav.NewPropFindResponse(ab.Path, propfind, props) } func (b *backend) propFindAllAddressBooks(ctx context.Context, propfind *internal.PropFind, recurse bool) ([]internal.Response, error) { @@ -553,19 +553,19 @@ func (b *backend) propFindAllAddressBooks(ctx context.Context, propfind *interna } func (b *backend) propFindAddressObject(ctx context.Context, propfind *internal.PropFind, ao *AddressObject) (*internal.Response, error) { - props := map[xml.Name]internal.PropFindFunc{ - internal.CurrentUserPrincipalName: func(*internal.RawXMLValue) (interface{}, error) { + props := map[xml.Name]webdav.PropFindFunc{ + internal.CurrentUserPrincipalName: func() (interface{}, error) { path, err := b.Backend.CurrentUserPrincipal(ctx) if err != nil { return nil, err } return &internal.CurrentUserPrincipal{Href: internal.Href{Path: path}}, nil }, - internal.GetContentTypeName: func(*internal.RawXMLValue) (interface{}, error) { + internal.GetContentTypeName: func() (interface{}, error) { return &internal.GetContentType{Type: vcard.MIMEType}, nil }, // TODO: address-data can only be used in REPORT requests - addressDataName: func(*internal.RawXMLValue) (interface{}, error) { + addressDataName: func() (interface{}, error) { var buf bytes.Buffer if err := vcard.NewEncoder(&buf).Encode(ao.Card); err != nil { return nil, err @@ -576,23 +576,23 @@ func (b *backend) propFindAddressObject(ctx context.Context, propfind *internal. } if ao.ContentLength > 0 { - props[internal.GetContentLengthName] = func(*internal.RawXMLValue) (interface{}, error) { + props[internal.GetContentLengthName] = func() (interface{}, error) { return &internal.GetContentLength{Length: ao.ContentLength}, nil } } if !ao.ModTime.IsZero() { - props[internal.GetLastModifiedName] = func(*internal.RawXMLValue) (interface{}, error) { + props[internal.GetLastModifiedName] = func() (interface{}, error) { return &internal.GetLastModified{LastModified: internal.Time(ao.ModTime)}, nil } } if ao.ETag != "" { - props[internal.GetETagName] = func(*internal.RawXMLValue) (interface{}, error) { + props[internal.GetETagName] = func() (interface{}, error) { return &internal.GetETag{ETag: internal.ETag(ao.ETag)}, nil } } - return internal.NewPropFindResponse(ao.Path, propfind, props) + return webdav.NewPropFindResponse(ao.Path, propfind, props) } func (b *backend) propFindAllAddressObjects(ctx context.Context, propfind *internal.PropFind, ab *AddressBook) ([]internal.Response, error) { diff --git a/internal/server.go b/internal/server.go index a27e771..f2df280 100644 --- a/internal/server.go +++ b/internal/server.go @@ -160,77 +160,6 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) error { return ServeMultiStatus(w, ms) } -type PropFindFunc func(raw *RawXMLValue) (interface{}, error) - -func NewPropFindResponse(path string, propfind *PropFind, props map[xml.Name]PropFindFunc) (*Response, error) { - resp := &Response{Hrefs: []Href{Href{Path: path}}} - - if _, ok := props[ResourceTypeName]; !ok { - props[ResourceTypeName] = func(*RawXMLValue) (interface{}, error) { - return NewResourceType(), nil - } - } - - if propfind.PropName != nil { - for xmlName, _ := range props { - emptyVal := NewRawXMLElement(xmlName, nil, nil) - if err := resp.EncodeProp(http.StatusOK, emptyVal); err != nil { - return nil, err - } - } - } else if propfind.AllProp != nil { - // TODO: add support for propfind.Include - for xmlName, f := range props { - emptyVal := NewRawXMLElement(xmlName, nil, nil) - - val, err := f(emptyVal) - - code := http.StatusOK - if err != nil { - // TODO: don't throw away error message here - code = HTTPErrorFromError(err).Code - val = emptyVal - } - - if err := resp.EncodeProp(code, val); err != nil { - return nil, err - } - } - } else if prop := propfind.Prop; prop != nil { - for _, raw := range prop.Raw { - xmlName, ok := raw.XMLName() - if !ok { - continue - } - - emptyVal := NewRawXMLElement(xmlName, nil, nil) - - var code int - var val interface{} = emptyVal - f, ok := props[xmlName] - if ok { - if v, err := f(&raw); err != nil { - // TODO: don't throw away error message here - code = HTTPErrorFromError(err).Code - } else { - code = http.StatusOK - val = v - } - } else { - code = http.StatusNotFound - } - - if err := resp.EncodeProp(code, val); err != nil { - return nil, err - } - } - } else { - return nil, HTTPErrorf(http.StatusBadRequest, "webdav: request missing propname, allprop or prop element") - } - - return resp, nil -} - func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) error { var update PropertyUpdate if err := DecodeXMLRequest(r, &update); err != nil { diff --git a/server.go b/server.go index 6341809..63dc0f0 100644 --- a/server.go +++ b/server.go @@ -51,6 +51,10 @@ func NewHTTPError(statusCode int, cause error) error { return &internal.HTTPError{Code: statusCode, Err: cause} } +type PropFindFunc func() (interface{}, error) + +type BuildPropFindFuncMapFunc func(fi *FileInfo) map[xml.Name]PropFindFunc + type backend struct { FileSystem FileSystem } @@ -151,9 +155,88 @@ func (b *backend) PropFind(r *http.Request, propfind *internal.PropFind, depth i } func (b *backend) propFindFile(propfind *internal.PropFind, fi *FileInfo) (*internal.Response, error) { - props := make(map[xml.Name]internal.PropFindFunc) + props := BuildPropFindFuncMap(fi) + if fi.BuildPropFindFuncMap != nil { + additionalProps := fi.BuildPropFindFuncMap(fi) + for k, v := range additionalProps { + props[k] = v + } + } + + return NewPropFindResponse(fi.Path, propfind, props) +} + +func NewPropFindResponse(path string, propfind *internal.PropFind, props map[xml.Name]PropFindFunc) (*internal.Response, error) { + resp := &internal.Response{Hrefs: []internal.Href{internal.Href{Path: path}}} + + if _, ok := props[internal.ResourceTypeName]; !ok { + props[internal.ResourceTypeName] = func() (interface{}, error) { + return internal.NewResourceType(), nil + } + } + + if propfind.PropName != nil { + for xmlName, _ := range props { + emptyVal := internal.NewRawXMLElement(xmlName, nil, nil) + if err := resp.EncodeProp(http.StatusOK, emptyVal); err != nil { + return nil, err + } + } + } else if propfind.AllProp != nil { + // TODO: add support for propfind.Include + for xmlName, f := range props { + val, err := f() + + code := http.StatusOK + if err != nil { + // TODO: don't throw away error message here + code = internal.HTTPErrorFromError(err).Code + emptyVal := internal.NewRawXMLElement(xmlName, nil, nil) + val = emptyVal + } - props[internal.ResourceTypeName] = func(*internal.RawXMLValue) (interface{}, error) { + if err := resp.EncodeProp(code, val); err != nil { + return nil, err + } + } + } else if prop := propfind.Prop; prop != nil { + for _, raw := range prop.Raw { + xmlName, ok := raw.XMLName() + if !ok { + continue + } + + emptyVal := internal.NewRawXMLElement(xmlName, nil, nil) + + var code int + var val interface{} = emptyVal + f, ok := props[xmlName] + if ok { + if v, err := f(); err != nil { + // TODO: don't throw away error message here + code = internal.HTTPErrorFromError(err).Code + } else { + code = http.StatusOK + val = v + } + } else { + code = http.StatusNotFound + } + + if err := resp.EncodeProp(code, val); err != nil { + return nil, err + } + } + } else { + return nil, internal.HTTPErrorf(http.StatusBadRequest, "webdav: request missing propname, allprop or prop element") + } + + return resp, nil +} + +func BuildPropFindFuncMap(fi *FileInfo) map[xml.Name]PropFindFunc { + props := make(map[xml.Name]PropFindFunc) + props[internal.ResourceTypeName] = func() (interface{}, error) { var types []xml.Name if fi.IsDir { types = append(types, internal.CollectionName) @@ -162,30 +245,29 @@ func (b *backend) propFindFile(propfind *internal.PropFind, fi *FileInfo) (*inte } if !fi.IsDir { - props[internal.GetContentLengthName] = func(*internal.RawXMLValue) (interface{}, error) { + props[internal.GetContentLengthName] = func() (interface{}, error) { return &internal.GetContentLength{Length: fi.Size}, nil } if !fi.ModTime.IsZero() { - props[internal.GetLastModifiedName] = func(*internal.RawXMLValue) (interface{}, error) { + props[internal.GetLastModifiedName] = func() (interface{}, error) { return &internal.GetLastModified{LastModified: internal.Time(fi.ModTime)}, nil } } if fi.MIMEType != "" { - props[internal.GetContentTypeName] = func(*internal.RawXMLValue) (interface{}, error) { + props[internal.GetContentTypeName] = func() (interface{}, error) { return &internal.GetContentType{Type: fi.MIMEType}, nil } } if fi.ETag != "" { - props[internal.GetETagName] = func(*internal.RawXMLValue) (interface{}, error) { + props[internal.GetETagName] = func() (interface{}, error) { return &internal.GetETag{ETag: internal.ETag(fi.ETag)}, nil } } } - - return internal.NewPropFindResponse(fi.Path, propfind, props) + return props } func (b *backend) PropPatch(r *http.Request, update *internal.PropertyUpdate) (*internal.Response, error) { @@ -303,11 +385,11 @@ func servePrincipalPropfind(w http.ResponseWriter, r *http.Request, options *Ser if err := internal.DecodeXMLRequest(r, &propfind); err != nil { return err } - props := map[xml.Name]internal.PropFindFunc{ - internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) { + props := map[xml.Name]PropFindFunc{ + internal.ResourceTypeName: func() (interface{}, error) { return internal.NewResourceType(principalName), nil }, - internal.CurrentUserPrincipalName: func(*internal.RawXMLValue) (interface{}, error) { + internal.CurrentUserPrincipalName: func() (interface{}, error) { return &internal.CurrentUserPrincipal{Href: internal.Href{Path: options.CurrentUserPrincipalPath}}, nil }, } @@ -316,12 +398,12 @@ func servePrincipalPropfind(w http.ResponseWriter, r *http.Request, options *Ser for _, homeSet := range options.HomeSets { hs := homeSet // capture variable for closure - props[homeSet.GetXMLName()] = func(*internal.RawXMLValue) (interface{}, error) { + props[homeSet.GetXMLName()] = func() (interface{}, error) { return hs, nil } } - resp, err := internal.NewPropFindResponse(r.URL.Path, &propfind, props) + resp, err := NewPropFindResponse(r.URL.Path, &propfind, props) if err != nil { return err } diff --git a/webdav.go b/webdav.go index e691ac5..cf16f5f 100644 --- a/webdav.go +++ b/webdav.go @@ -11,12 +11,13 @@ import ( // FileInfo holds information about a WebDAV file. type FileInfo struct { - Path string - Size int64 - ModTime time.Time - IsDir bool - MIMEType string - ETag string + Path string + Size int64 + ModTime time.Time + IsDir bool + MIMEType string + ETag string + BuildPropFindFuncMap BuildPropFindFuncMapFunc } type CopyOptions struct {