mirror of
https://github.com/1f349/go-webdav.git
synced 2025-04-01 17:55:05 +01:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
6f60a899bf | |||
d28f08a32d | |||
f5b508b766 | |||
63f15c0ec6 | |||
906087cd59 | |||
|
3cc7466ac9 | ||
|
9d778f4072 | ||
|
93fee5bcf0 | ||
|
7f8c17ad71 | ||
|
810c51fa2d |
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,3 +12,4 @@
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
.idea/
|
||||
|
@ -1,7 +1,6 @@
|
||||
# go-webdav
|
||||
|
||||
[](https://pkg.go.dev/github.com/emersion/go-webdav)
|
||||
[](https://builds.sr.ht/~emersion/go-webdav/commits/master?)
|
||||
|
||||
A Go library for [WebDAV], [CalDAV] and [CardDAV].
|
||||
|
||||
|
103
caldav/server.go
103
caldav/server.go
@ -465,12 +465,10 @@ func (b *backend) propFindRoot(ctx context.Context, propfind *internal.PropFind)
|
||||
}
|
||||
|
||||
props := map[xml.Name]internal.PropFindFunc{
|
||||
internal.CurrentUserPrincipalName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.CurrentUserPrincipal{Href: internal.Href{Path: principalPath}}, nil
|
||||
},
|
||||
internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return internal.NewResourceType(internal.CollectionName), nil
|
||||
},
|
||||
internal.CurrentUserPrincipalName: internal.PropFindValue(&internal.CurrentUserPrincipal{
|
||||
Href: internal.Href{Path: principalPath},
|
||||
}),
|
||||
internal.ResourceTypeName: internal.PropFindValue(internal.NewResourceType(internal.CollectionName)),
|
||||
}
|
||||
return internal.NewPropFindResponse(principalPath, propfind, props)
|
||||
}
|
||||
@ -486,15 +484,13 @@ func (b *backend) propFindUserPrincipal(ctx context.Context, propfind *internal.
|
||||
}
|
||||
|
||||
props := map[xml.Name]internal.PropFindFunc{
|
||||
internal.CurrentUserPrincipalName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.CurrentUserPrincipal{Href: internal.Href{Path: principalPath}}, nil
|
||||
},
|
||||
calendarHomeSetName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &calendarHomeSet{Href: internal.Href{Path: homeSetPath}}, nil
|
||||
},
|
||||
internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return internal.NewResourceType(internal.CollectionName), nil
|
||||
},
|
||||
internal.CurrentUserPrincipalName: internal.PropFindValue(&internal.CurrentUserPrincipal{
|
||||
Href: internal.Href{Path: principalPath},
|
||||
}),
|
||||
calendarHomeSetName: internal.PropFindValue(&calendarHomeSet{
|
||||
Href: internal.Href{Path: homeSetPath},
|
||||
}),
|
||||
internal.ResourceTypeName: internal.PropFindValue(internal.NewResourceType(internal.CollectionName)),
|
||||
}
|
||||
return internal.NewPropFindResponse(principalPath, propfind, props)
|
||||
}
|
||||
@ -511,12 +507,10 @@ 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) {
|
||||
return &internal.CurrentUserPrincipal{Href: internal.Href{Path: principalPath}}, nil
|
||||
},
|
||||
internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return internal.NewResourceType(internal.CollectionName), nil
|
||||
},
|
||||
internal.CurrentUserPrincipalName: internal.PropFindValue(&internal.CurrentUserPrincipal{
|
||||
Href: internal.Href{Path: principalPath},
|
||||
}),
|
||||
internal.ResourceTypeName: internal.PropFindValue(internal.NewResourceType(internal.CollectionName)),
|
||||
}
|
||||
return internal.NewPropFindResponse(homeSetPath, propfind, props)
|
||||
}
|
||||
@ -530,19 +524,15 @@ func (b *backend) propFindCalendar(ctx context.Context, propfind *internal.PropF
|
||||
}
|
||||
return &internal.CurrentUserPrincipal{Href: internal.Href{Path: path}}, nil
|
||||
},
|
||||
internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return internal.NewResourceType(internal.CollectionName, calendarName), nil
|
||||
},
|
||||
calendarDescriptionName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &calendarDescription{Description: cal.Description}, nil
|
||||
},
|
||||
supportedCalendarDataName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &supportedCalendarData{
|
||||
Types: []calendarDataType{
|
||||
{ContentType: ical.MIMEType, Version: "2.0"},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
internal.ResourceTypeName: internal.PropFindValue(internal.NewResourceType(internal.CollectionName, calendarName)),
|
||||
calendarDescriptionName: internal.PropFindValue(&calendarDescription{
|
||||
Description: cal.Description,
|
||||
}),
|
||||
supportedCalendarDataName: internal.PropFindValue(&supportedCalendarData{
|
||||
Types: []calendarDataType{
|
||||
{ContentType: ical.MIMEType, Version: "2.0"},
|
||||
},
|
||||
}),
|
||||
supportedCalendarComponentSetName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
components := []comp{}
|
||||
if cal.SupportedComponentSet != nil {
|
||||
@ -559,19 +549,22 @@ func (b *backend) propFindCalendar(ctx context.Context, propfind *internal.PropF
|
||||
}
|
||||
|
||||
if cal.Name != "" {
|
||||
props[internal.DisplayNameName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.DisplayName{Name: cal.Name}, nil
|
||||
}
|
||||
props[internal.DisplayNameName] = internal.PropFindValue(&internal.DisplayName{
|
||||
Name: cal.Name,
|
||||
})
|
||||
}
|
||||
if cal.Description != "" {
|
||||
props[calendarDescriptionName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &calendarDescription{Description: cal.Description}, nil
|
||||
}
|
||||
props[calendarDescriptionName] = internal.PropFindValue(&calendarDescription{
|
||||
Description: cal.Description,
|
||||
})
|
||||
}
|
||||
if cal.MaxResourceSize > 0 {
|
||||
props[maxResourceSizeName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &maxResourceSize{Size: cal.MaxResourceSize}, nil
|
||||
}
|
||||
props[maxResourceSizeName] = internal.PropFindValue(&maxResourceSize{
|
||||
Size: cal.MaxResourceSize,
|
||||
})
|
||||
}
|
||||
props[internal.CurrentUserPrivilegeSetName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.CurrentUserPrivilegeSet{Privilege: internal.NewAllPrivileges()}, 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
|
||||
@ -612,9 +605,9 @@ func (b *backend) propFindCalendarObject(ctx context.Context, propfind *internal
|
||||
}
|
||||
return &internal.CurrentUserPrincipal{Href: internal.Href{Path: path}}, nil
|
||||
},
|
||||
internal.GetContentTypeName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.GetContentType{Type: ical.MIMEType}, nil
|
||||
},
|
||||
internal.GetContentTypeName: internal.PropFindValue(&internal.GetContentType{
|
||||
Type: ical.MIMEType,
|
||||
}),
|
||||
// TODO: calendar-data can only be used in REPORT requests
|
||||
calendarDataName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
var buf bytes.Buffer
|
||||
@ -627,20 +620,20 @@ func (b *backend) propFindCalendarObject(ctx context.Context, propfind *internal
|
||||
}
|
||||
|
||||
if co.ContentLength > 0 {
|
||||
props[internal.GetContentLengthName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.GetContentLength{Length: co.ContentLength}, nil
|
||||
}
|
||||
props[internal.GetContentLengthName] = internal.PropFindValue(&internal.GetContentLength{
|
||||
Length: co.ContentLength,
|
||||
})
|
||||
}
|
||||
if !co.ModTime.IsZero() {
|
||||
props[internal.GetLastModifiedName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.GetLastModified{LastModified: internal.Time(co.ModTime)}, nil
|
||||
}
|
||||
props[internal.GetLastModifiedName] = internal.PropFindValue(&internal.GetLastModified{
|
||||
LastModified: internal.Time(co.ModTime),
|
||||
})
|
||||
}
|
||||
|
||||
if co.ETag != "" {
|
||||
props[internal.GetETagName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.GetETag{ETag: internal.ETag(co.ETag)}, nil
|
||||
}
|
||||
props[internal.GetETagName] = internal.PropFindValue(&internal.GetETag{
|
||||
ETag: internal.ETag(co.ETag),
|
||||
})
|
||||
}
|
||||
|
||||
return internal.NewPropFindResponse(co.Path, propfind, props)
|
||||
|
@ -431,12 +431,10 @@ func (b *backend) propFindRoot(ctx context.Context, propfind *internal.PropFind)
|
||||
}
|
||||
|
||||
props := map[xml.Name]internal.PropFindFunc{
|
||||
internal.CurrentUserPrincipalName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.CurrentUserPrincipal{Href: internal.Href{Path: principalPath}}, nil
|
||||
},
|
||||
internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return internal.NewResourceType(internal.CollectionName), nil
|
||||
},
|
||||
internal.CurrentUserPrincipalName: internal.PropFindValue(&internal.CurrentUserPrincipal{
|
||||
Href: internal.Href{Path: principalPath},
|
||||
}),
|
||||
internal.ResourceTypeName: internal.PropFindValue(internal.NewResourceType(internal.CollectionName)),
|
||||
}
|
||||
return internal.NewPropFindResponse(principalPath, propfind, props)
|
||||
}
|
||||
@ -446,30 +444,24 @@ func (b *backend) propFindUserPrincipal(ctx context.Context, propfind *internal.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
homeSetPath, err := b.Backend.AddressBookHomeSetPath(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
props := map[xml.Name]internal.PropFindFunc{
|
||||
internal.CurrentUserPrincipalName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.CurrentUserPrincipal{Href: internal.Href{Path: principalPath}}, nil
|
||||
},
|
||||
internal.CurrentUserPrincipalName: internal.PropFindValue(&internal.CurrentUserPrincipal{
|
||||
Href: internal.Href{Path: principalPath},
|
||||
}),
|
||||
addressBookHomeSetName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
homeSetPath, err := b.Backend.AddressBookHomeSetPath(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &addressbookHomeSet{Href: internal.Href{Path: homeSetPath}}, nil
|
||||
},
|
||||
internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return internal.NewResourceType(internal.CollectionName), nil
|
||||
},
|
||||
internal.ResourceTypeName: internal.PropFindValue(internal.NewResourceType(internal.CollectionName)),
|
||||
}
|
||||
return internal.NewPropFindResponse(principalPath, propfind, props)
|
||||
}
|
||||
|
||||
func (b *backend) propFindHomeSet(ctx context.Context, propfind *internal.PropFind) (*internal.Response, error) {
|
||||
principalPath, err := b.Backend.CurrentUserPrincipal(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
homeSetPath, err := b.Backend.AddressBookHomeSetPath(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -478,11 +470,13 @@ 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) {
|
||||
principalPath, err := b.Backend.CurrentUserPrincipal(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &internal.CurrentUserPrincipal{Href: internal.Href{Path: principalPath}}, nil
|
||||
},
|
||||
internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return internal.NewResourceType(internal.CollectionName), nil
|
||||
},
|
||||
internal.ResourceTypeName: internal.PropFindValue(internal.NewResourceType(internal.CollectionName)),
|
||||
}
|
||||
return internal.NewPropFindResponse(homeSetPath, propfind, props)
|
||||
}
|
||||
@ -496,33 +490,32 @@ func (b *backend) propFindAddressBook(ctx context.Context, propfind *internal.Pr
|
||||
}
|
||||
return &internal.CurrentUserPrincipal{Href: internal.Href{Path: path}}, nil
|
||||
},
|
||||
internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return internal.NewResourceType(internal.CollectionName, addressBookName), nil
|
||||
},
|
||||
supportedAddressDataName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &supportedAddressData{
|
||||
Types: []addressDataType{
|
||||
{ContentType: vcard.MIMEType, Version: "3.0"},
|
||||
{ContentType: vcard.MIMEType, Version: "4.0"},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
internal.ResourceTypeName: internal.PropFindValue(internal.NewResourceType(internal.CollectionName, addressBookName)),
|
||||
supportedAddressDataName: internal.PropFindValue(&supportedAddressData{
|
||||
Types: []addressDataType{
|
||||
{ContentType: vcard.MIMEType, Version: "3.0"},
|
||||
{ContentType: vcard.MIMEType, Version: "4.0"},
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
if ab.Name != "" {
|
||||
props[internal.DisplayNameName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.DisplayName{Name: ab.Name}, nil
|
||||
}
|
||||
props[internal.DisplayNameName] = internal.PropFindValue(&internal.DisplayName{
|
||||
Name: ab.Name,
|
||||
})
|
||||
}
|
||||
if ab.Description != "" {
|
||||
props[addressBookDescriptionName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &addressbookDescription{Description: ab.Description}, nil
|
||||
}
|
||||
props[addressBookDescriptionName] = internal.PropFindValue(&addressbookDescription{
|
||||
Description: ab.Description,
|
||||
})
|
||||
}
|
||||
if ab.MaxResourceSize > 0 {
|
||||
props[maxResourceSizeName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &maxResourceSize{Size: ab.MaxResourceSize}, nil
|
||||
}
|
||||
props[maxResourceSizeName] = internal.PropFindValue(&maxResourceSize{
|
||||
Size: ab.MaxResourceSize,
|
||||
})
|
||||
}
|
||||
props[internal.CurrentUserPrivilegeSetName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.CurrentUserPrivilegeSet{Privilege: internal.NewAllPrivileges()}, nil
|
||||
}
|
||||
|
||||
return internal.NewPropFindResponse(ab.Path, propfind, props)
|
||||
@ -561,9 +554,9 @@ func (b *backend) propFindAddressObject(ctx context.Context, propfind *internal.
|
||||
}
|
||||
return &internal.CurrentUserPrincipal{Href: internal.Href{Path: path}}, nil
|
||||
},
|
||||
internal.GetContentTypeName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.GetContentType{Type: vcard.MIMEType}, nil
|
||||
},
|
||||
internal.GetContentTypeName: internal.PropFindValue(&internal.GetContentType{
|
||||
Type: vcard.MIMEType,
|
||||
}),
|
||||
// TODO: address-data can only be used in REPORT requests
|
||||
addressDataName: func(*internal.RawXMLValue) (interface{}, error) {
|
||||
var buf bytes.Buffer
|
||||
@ -576,20 +569,20 @@ func (b *backend) propFindAddressObject(ctx context.Context, propfind *internal.
|
||||
}
|
||||
|
||||
if ao.ContentLength > 0 {
|
||||
props[internal.GetContentLengthName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.GetContentLength{Length: ao.ContentLength}, nil
|
||||
}
|
||||
props[internal.GetContentLengthName] = internal.PropFindValue(&internal.GetContentLength{
|
||||
Length: ao.ContentLength,
|
||||
})
|
||||
}
|
||||
if !ao.ModTime.IsZero() {
|
||||
props[internal.GetLastModifiedName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.GetLastModified{LastModified: internal.Time(ao.ModTime)}, nil
|
||||
}
|
||||
props[internal.GetLastModifiedName] = internal.PropFindValue(&internal.GetLastModified{
|
||||
LastModified: internal.Time(ao.ModTime),
|
||||
})
|
||||
}
|
||||
|
||||
if ao.ETag != "" {
|
||||
props[internal.GetETagName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.GetETag{ETag: internal.ETag(ao.ETag)}, nil
|
||||
}
|
||||
props[internal.GetETagName] = internal.PropFindValue(&internal.GetETag{
|
||||
ETag: internal.ETag(ao.ETag),
|
||||
})
|
||||
}
|
||||
|
||||
return internal.NewPropFindResponse(ao.Path, propfind, props)
|
||||
|
28
fs_local.go
28
fs_local.go
@ -114,15 +114,31 @@ func (fs LocalFileSystem) ReadDir(ctx context.Context, name string, recursive bo
|
||||
return l, errFromOS(err)
|
||||
}
|
||||
|
||||
func (fs LocalFileSystem) Create(ctx context.Context, name string, body io.ReadCloser) (*FileInfo, bool, error) {
|
||||
func (fs LocalFileSystem) Create(ctx context.Context, name string, body io.ReadCloser, opts *CreateOptions) (fi *FileInfo, created bool, err error) {
|
||||
p, err := fs.localPath(name)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
created := false
|
||||
fi, _ := fs.Stat(ctx, name)
|
||||
if fi == nil {
|
||||
created = true
|
||||
fi, _ = fs.Stat(ctx, name)
|
||||
created = fi == nil
|
||||
etag := ""
|
||||
if fi != nil {
|
||||
etag = fi.ETag
|
||||
}
|
||||
|
||||
if opts.IfMatch.IsSet() {
|
||||
if ok, err := opts.IfMatch.MatchETag(etag); err != nil {
|
||||
return nil, false, NewHTTPError(http.StatusBadRequest, err)
|
||||
} else if !ok {
|
||||
return nil, false, NewHTTPError(http.StatusPreconditionFailed, fmt.Errorf("If-Match condition failed"))
|
||||
}
|
||||
}
|
||||
if opts.IfNoneMatch.IsSet() {
|
||||
if ok, err := opts.IfNoneMatch.MatchETag(etag); err != nil {
|
||||
return nil, false, NewHTTPError(http.StatusBadRequest, err)
|
||||
} else if ok {
|
||||
return nil, false, NewHTTPError(http.StatusPreconditionFailed, fmt.Errorf("If-None-Match condition failed"))
|
||||
}
|
||||
}
|
||||
|
||||
wc, err := os.Create(p)
|
||||
@ -132,9 +148,11 @@ func (fs LocalFileSystem) Create(ctx context.Context, name string, body io.ReadC
|
||||
defer wc.Close()
|
||||
|
||||
if _, err := io.Copy(wc, body); err != nil {
|
||||
os.Remove(p)
|
||||
return nil, false, err
|
||||
}
|
||||
if err := wc.Close(); err != nil {
|
||||
os.Remove(p)
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -22,6 +23,8 @@ var (
|
||||
GetETagName = xml.Name{Namespace, "getetag"}
|
||||
|
||||
CurrentUserPrincipalName = xml.Name{Namespace, "current-user-principal"}
|
||||
|
||||
CurrentUserPrivilegeSetName = xml.Name{Namespace, "current-user-privilege-set"}
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
@ -351,7 +354,7 @@ type Time time.Time
|
||||
func (t *Time) UnmarshalText(b []byte) error {
|
||||
tt, err := http.ParseTime(string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.New(err.Error() + " : time_data : " + base64.StdEncoding.EncodeToString(b))
|
||||
}
|
||||
*t = Time(tt)
|
||||
return nil
|
||||
@ -417,6 +420,30 @@ type CurrentUserPrincipal struct {
|
||||
Unauthenticated *struct{} `xml:"unauthenticated,omitempty"`
|
||||
}
|
||||
|
||||
type CurrentUserPrivilegeSet struct {
|
||||
XMLName xml.Name `xml:"DAV: current-user-privilege-set"`
|
||||
Privilege []Privilege `xml:"privilege"`
|
||||
}
|
||||
|
||||
type Privilege struct {
|
||||
XMLName xml.Name `xml:"DAV: privilege"`
|
||||
Read *struct{} `xml:"DAV: read,omitempty"`
|
||||
All *struct{} `xml:"DAV: all,omitempty"`
|
||||
Write *struct{} `xml:"DAV: write,omitempty"`
|
||||
WriteProperties *struct{} `xml:"DAV: write-properties,omitempty"`
|
||||
WriteContent *struct{} `xml:"DAV: write-content,omitempty"`
|
||||
}
|
||||
|
||||
func NewAllPrivileges() []Privilege {
|
||||
return []Privilege{
|
||||
{Read: &struct{}{}},
|
||||
{All: &struct{}{}},
|
||||
{Write: &struct{}{}},
|
||||
{WriteProperties: &struct{}{}},
|
||||
{WriteContent: &struct{}{}},
|
||||
}
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc4918#section-14.19
|
||||
type PropertyUpdate struct {
|
||||
XMLName xml.Name `xml:"DAV: propertyupdate"`
|
||||
|
@ -125,7 +125,7 @@ func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) error {
|
||||
|
||||
w.Header().Add("DAV", strings.Join(caps, ", "))
|
||||
w.Header().Add("Allow", strings.Join(allow, ", "))
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -162,13 +162,17 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) error {
|
||||
|
||||
type PropFindFunc func(raw *RawXMLValue) (interface{}, error)
|
||||
|
||||
func PropFindValue(value interface{}) PropFindFunc {
|
||||
return func(raw *RawXMLValue) (interface{}, error) {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
props[ResourceTypeName] = PropFindValue(NewResourceType())
|
||||
}
|
||||
|
||||
if propfind.PropName != nil {
|
||||
@ -187,9 +191,8 @@ func NewPropFindResponse(path string, propfind *PropFind, props map[xml.Name]Pro
|
||||
|
||||
code := http.StatusOK
|
||||
if err != nil {
|
||||
// TODO: don't throw away error message here
|
||||
code = HTTPErrorFromError(err).Code
|
||||
val = emptyVal
|
||||
val = NewRawXMLElement(xmlName, []xml.Attr{{Name: xml.Name{Space: "ERR", Local: "Error"}, Value: err.Error()}}, nil)
|
||||
}
|
||||
|
||||
if err := resp.EncodeProp(code, val); err != nil {
|
||||
@ -210,8 +213,8 @@ func NewPropFindResponse(path string, propfind *PropFind, props map[xml.Name]Pro
|
||||
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
|
||||
val = NewRawXMLElement(xmlName, []xml.Attr{{Name: xml.Name{Space: "ERR", Local: "Error"}, Value: err.Error()}}, nil)
|
||||
} else {
|
||||
code = http.StatusOK
|
||||
val = v
|
||||
|
38
server.go
38
server.go
@ -17,7 +17,7 @@ type FileSystem interface {
|
||||
Open(ctx context.Context, name string) (io.ReadCloser, error)
|
||||
Stat(ctx context.Context, name string) (*FileInfo, error)
|
||||
ReadDir(ctx context.Context, name string, recursive bool) ([]FileInfo, error)
|
||||
Create(ctx context.Context, name string, body io.ReadCloser) (fileInfo *FileInfo, created bool, err error)
|
||||
Create(ctx context.Context, name string, body io.ReadCloser, opts *CreateOptions) (fileInfo *FileInfo, created bool, err error)
|
||||
RemoveAll(ctx context.Context, name string) error
|
||||
Mkdir(ctx context.Context, name string) error
|
||||
Copy(ctx context.Context, name, dest string, options *CopyOptions) (created bool, err error)
|
||||
@ -162,26 +162,26 @@ func (b *backend) propFindFile(propfind *internal.PropFind, fi *FileInfo) (*inte
|
||||
}
|
||||
|
||||
if !fi.IsDir {
|
||||
props[internal.GetContentLengthName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.GetContentLength{Length: fi.Size}, nil
|
||||
}
|
||||
props[internal.GetContentLengthName] = internal.PropFindValue(&internal.GetContentLength{
|
||||
Length: fi.Size,
|
||||
})
|
||||
|
||||
if !fi.ModTime.IsZero() {
|
||||
props[internal.GetLastModifiedName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.GetLastModified{LastModified: internal.Time(fi.ModTime)}, nil
|
||||
}
|
||||
props[internal.GetLastModifiedName] = internal.PropFindValue(&internal.GetLastModified{
|
||||
LastModified: internal.Time(fi.ModTime),
|
||||
})
|
||||
}
|
||||
|
||||
if fi.MIMEType != "" {
|
||||
props[internal.GetContentTypeName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.GetContentType{Type: fi.MIMEType}, nil
|
||||
}
|
||||
props[internal.GetContentTypeName] = internal.PropFindValue(&internal.GetContentType{
|
||||
Type: fi.MIMEType,
|
||||
})
|
||||
}
|
||||
|
||||
if fi.ETag != "" {
|
||||
props[internal.GetETagName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||
return &internal.GetETag{ETag: internal.ETag(fi.ETag)}, nil
|
||||
}
|
||||
props[internal.GetETagName] = internal.PropFindValue(&internal.GetETag{
|
||||
ETag: internal.ETag(fi.ETag),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,12 +194,18 @@ func (b *backend) PropPatch(r *http.Request, update *internal.PropertyUpdate) (*
|
||||
}
|
||||
|
||||
func (b *backend) Put(w http.ResponseWriter, r *http.Request) error {
|
||||
fi, created, err := b.FileSystem.Create(r.Context(), r.URL.Path, r.Body)
|
||||
ifNoneMatch := ConditionalMatch(r.Header.Get("If-None-Match"))
|
||||
ifMatch := ConditionalMatch(r.Header.Get("If-Match"))
|
||||
|
||||
opts := CreateOptions{
|
||||
IfNoneMatch: ifNoneMatch,
|
||||
IfMatch: ifMatch,
|
||||
}
|
||||
fi, created, err := b.FileSystem.Create(r.Context(), r.URL.Path, r.Body, &opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(fi.Size, 10))
|
||||
if fi.MIMEType != "" {
|
||||
w.Header().Set("Content-Type", fi.MIMEType)
|
||||
}
|
||||
@ -293,7 +299,7 @@ func ServePrincipal(w http.ResponseWriter, r *http.Request, options *ServePrinci
|
||||
allow := []string{http.MethodOptions, "PROPFIND", "REPORT", "DELETE", "MKCOL"}
|
||||
w.Header().Add("DAV", strings.Join(caps, ", "))
|
||||
w.Header().Add("Allow", strings.Join(allow, ", "))
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case "PROPFIND":
|
||||
if err := servePrincipalPropfind(w, r, options); err != nil {
|
||||
internal.ServeError(w, err)
|
||||
|
13
webdav.go
13
webdav.go
@ -19,6 +19,11 @@ type FileInfo struct {
|
||||
ETag string
|
||||
}
|
||||
|
||||
type CreateOptions struct {
|
||||
IfMatch ConditionalMatch
|
||||
IfNoneMatch ConditionalMatch
|
||||
}
|
||||
|
||||
type CopyOptions struct {
|
||||
NoRecursive bool
|
||||
NoOverwrite bool
|
||||
@ -48,3 +53,11 @@ func (val ConditionalMatch) ETag() (string, error) {
|
||||
}
|
||||
return string(e), nil
|
||||
}
|
||||
|
||||
func (val ConditionalMatch) MatchETag(etag string) (bool, error) {
|
||||
if val.IsWildcard() {
|
||||
return true, nil
|
||||
}
|
||||
t, err := val.ETag()
|
||||
return t == etag, err
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user