mirror of
https://github.com/1f349/go-webdav.git
synced 2024-12-22 08:14:15 +00:00
carddav, caldav: add missing headers on PUT
ETag and Last-Modified should be set to the new calendar object or address object properties.
This commit is contained in:
parent
25f1014ef2
commit
3ed9a4f052
@ -38,7 +38,7 @@ type Backend interface {
|
|||||||
GetCalendarObject(ctx context.Context, path string, req *CalendarCompRequest) (*CalendarObject, error)
|
GetCalendarObject(ctx context.Context, path string, req *CalendarCompRequest) (*CalendarObject, error)
|
||||||
ListCalendarObjects(ctx context.Context, path string, req *CalendarCompRequest) ([]CalendarObject, error)
|
ListCalendarObjects(ctx context.Context, path string, req *CalendarCompRequest) ([]CalendarObject, error)
|
||||||
QueryCalendarObjects(ctx context.Context, path string, query *CalendarQuery) ([]CalendarObject, error)
|
QueryCalendarObjects(ctx context.Context, path string, query *CalendarQuery) ([]CalendarObject, error)
|
||||||
PutCalendarObject(ctx context.Context, path string, calendar *ical.Calendar, opts *PutCalendarObjectOptions) (loc string, err error)
|
PutCalendarObject(ctx context.Context, path string, calendar *ical.Calendar, opts *PutCalendarObjectOptions) (*CalendarObject, error)
|
||||||
DeleteCalendarObject(ctx context.Context, path string) error
|
DeleteCalendarObject(ctx context.Context, path string) error
|
||||||
|
|
||||||
webdav.UserPrincipalBackend
|
webdav.UserPrincipalBackend
|
||||||
@ -78,7 +78,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
Backend: h.Backend,
|
Backend: h.Backend,
|
||||||
Prefix: strings.TrimSuffix(h.Prefix, "/"),
|
Prefix: strings.TrimSuffix(h.Prefix, "/"),
|
||||||
}
|
}
|
||||||
hh := internal.Handler{&b}
|
hh := internal.Handler{Backend: &b}
|
||||||
hh.ServeHTTP(w, r)
|
hh.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -668,7 +668,7 @@ func (b *backend) PropPatch(r *http.Request, update *internal.PropertyUpdate) (*
|
|||||||
return nil, internal.HTTPErrorf(http.StatusNotImplemented, "caldav: PropPatch not implemented")
|
return nil, internal.HTTPErrorf(http.StatusNotImplemented, "caldav: PropPatch not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *backend) Put(r *http.Request) (*internal.Href, error) {
|
func (b *backend) Put(w http.ResponseWriter, r *http.Request) error {
|
||||||
ifNoneMatch := webdav.ConditionalMatch(r.Header.Get("If-None-Match"))
|
ifNoneMatch := webdav.ConditionalMatch(r.Header.Get("If-None-Match"))
|
||||||
ifMatch := webdav.ConditionalMatch(r.Header.Get("If-Match"))
|
ifMatch := webdav.ConditionalMatch(r.Header.Get("If-Match"))
|
||||||
|
|
||||||
@ -679,26 +679,39 @@ func (b *backend) Put(r *http.Request) (*internal.Href, error) {
|
|||||||
|
|
||||||
t, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
t, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, internal.HTTPErrorf(http.StatusBadRequest, "caldav: malformed Content-Type: %v", err)
|
return internal.HTTPErrorf(http.StatusBadRequest, "caldav: malformed Content-Type: %v", err)
|
||||||
}
|
}
|
||||||
if t != ical.MIMEType {
|
if t != ical.MIMEType {
|
||||||
// TODO: send CALDAV:supported-calendar-data error
|
// TODO: send CALDAV:supported-calendar-data error
|
||||||
return nil, internal.HTTPErrorf(http.StatusBadRequest, "caldav: unsupported Content-Type %q", t)
|
return internal.HTTPErrorf(http.StatusBadRequest, "caldav: unsupported Content-Type %q", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check CALDAV:max-resource-size precondition
|
// TODO: check CALDAV:max-resource-size precondition
|
||||||
cal, err := ical.NewDecoder(r.Body).Decode()
|
cal, err := ical.NewDecoder(r.Body).Decode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: send CALDAV:valid-calendar-data error
|
// TODO: send CALDAV:valid-calendar-data error
|
||||||
return nil, internal.HTTPErrorf(http.StatusBadRequest, "caldav: failed to parse iCalendar: %v", err)
|
return internal.HTTPErrorf(http.StatusBadRequest, "caldav: failed to parse iCalendar: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
loc, err := b.Backend.PutCalendarObject(r.Context(), r.URL.Path, cal, &opts)
|
co, err := b.Backend.PutCalendarObject(r.Context(), r.URL.Path, cal, &opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &internal.Href{Path: loc}, nil
|
if co.ETag != "" {
|
||||||
|
w.Header().Set("ETag", internal.ETag(co.ETag).String())
|
||||||
|
}
|
||||||
|
if !co.ModTime.IsZero() {
|
||||||
|
w.Header().Set("Last-Modified", co.ModTime.UTC().Format(http.TimeFormat))
|
||||||
|
}
|
||||||
|
if co.Path != "" {
|
||||||
|
w.Header().Set("Location", co.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: http.StatusNoContent if the resource already existed
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *backend) Delete(r *http.Request) error {
|
func (b *backend) Delete(r *http.Request) error {
|
||||||
@ -756,7 +769,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func NewPreconditionError(err PreconditionType) error {
|
func NewPreconditionError(err PreconditionType) error {
|
||||||
name := xml.Name{"urn:ietf:params:xml:ns:caldav", string(err)}
|
name := xml.Name{Space: "urn:ietf:params:xml:ns:caldav", Local: string(err)}
|
||||||
elem := internal.NewRawXMLElement(name, nil, nil)
|
elem := internal.NewRawXMLElement(name, nil, nil)
|
||||||
return &internal.HTTPError{
|
return &internal.HTTPError{
|
||||||
Code: 409,
|
Code: 409,
|
||||||
|
@ -222,8 +222,8 @@ func (t testBackend) GetCalendarObject(ctx context.Context, path string, req *Ca
|
|||||||
return nil, fmt.Errorf("Couldn't find calendar object at: %s", path)
|
return nil, fmt.Errorf("Couldn't find calendar object at: %s", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t testBackend) PutCalendarObject(ctx context.Context, path string, calendar *ical.Calendar, opts *PutCalendarObjectOptions) (string, error) {
|
func (t testBackend) PutCalendarObject(ctx context.Context, path string, calendar *ical.Calendar, opts *PutCalendarObjectOptions) (*CalendarObject, error) {
|
||||||
return "", nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t testBackend) ListCalendarObjects(ctx context.Context, path string, req *CalendarCompRequest) ([]CalendarObject, error) {
|
func (t testBackend) ListCalendarObjects(ctx context.Context, path string, req *CalendarCompRequest) ([]CalendarObject, error) {
|
||||||
|
@ -108,7 +108,7 @@ func (*testBackend) QueryAddressObjects(ctx context.Context, path string, query
|
|||||||
panic("TODO: implement")
|
panic("TODO: implement")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*testBackend) PutAddressObject(ctx context.Context, path string, card vcard.Card, opts *PutAddressObjectOptions) (loc string, err error) {
|
func (*testBackend) PutAddressObject(ctx context.Context, path string, card vcard.Card, opts *PutAddressObjectOptions) (*AddressObject, error) {
|
||||||
panic("TODO: implement")
|
panic("TODO: implement")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ type Backend interface {
|
|||||||
GetAddressObject(ctx context.Context, path string, req *AddressDataRequest) (*AddressObject, error)
|
GetAddressObject(ctx context.Context, path string, req *AddressDataRequest) (*AddressObject, error)
|
||||||
ListAddressObjects(ctx context.Context, path string, req *AddressDataRequest) ([]AddressObject, error)
|
ListAddressObjects(ctx context.Context, path string, req *AddressDataRequest) ([]AddressObject, error)
|
||||||
QueryAddressObjects(ctx context.Context, path string, query *AddressBookQuery) ([]AddressObject, error)
|
QueryAddressObjects(ctx context.Context, path string, query *AddressBookQuery) ([]AddressObject, error)
|
||||||
PutAddressObject(ctx context.Context, path string, card vcard.Card, opts *PutAddressObjectOptions) (loc string, err error)
|
PutAddressObject(ctx context.Context, path string, card vcard.Card, opts *PutAddressObjectOptions) (*AddressObject, error)
|
||||||
DeleteAddressObject(ctx context.Context, path string) error
|
DeleteAddressObject(ctx context.Context, path string) error
|
||||||
|
|
||||||
webdav.UserPrincipalBackend
|
webdav.UserPrincipalBackend
|
||||||
@ -75,7 +75,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
Backend: h.Backend,
|
Backend: h.Backend,
|
||||||
Prefix: strings.TrimSuffix(h.Prefix, "/"),
|
Prefix: strings.TrimSuffix(h.Prefix, "/"),
|
||||||
}
|
}
|
||||||
hh := internal.Handler{&b}
|
hh := internal.Handler{Backend: &b}
|
||||||
hh.ServeHTTP(w, r)
|
hh.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +170,7 @@ func (h *Handler) handleQuery(r *http.Request, w http.ResponseWriter, query *add
|
|||||||
for _, el := range query.Filter.Props {
|
for _, el := range query.Filter.Props {
|
||||||
pf, err := decodePropFilter(&el)
|
pf, err := decodePropFilter(&el)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &internal.HTTPError{http.StatusBadRequest, err}
|
return &internal.HTTPError{Code: http.StatusBadRequest, Err: err}
|
||||||
}
|
}
|
||||||
q.PropFilters = append(q.PropFilters, *pf)
|
q.PropFilters = append(q.PropFilters, *pf)
|
||||||
}
|
}
|
||||||
@ -653,7 +653,7 @@ func (b *backend) PropPatch(r *http.Request, update *internal.PropertyUpdate) (*
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *backend) Put(r *http.Request) (*internal.Href, error) {
|
func (b *backend) Put(w http.ResponseWriter, r *http.Request) error {
|
||||||
ifNoneMatch := webdav.ConditionalMatch(r.Header.Get("If-None-Match"))
|
ifNoneMatch := webdav.ConditionalMatch(r.Header.Get("If-None-Match"))
|
||||||
ifMatch := webdav.ConditionalMatch(r.Header.Get("If-Match"))
|
ifMatch := webdav.ConditionalMatch(r.Header.Get("If-Match"))
|
||||||
|
|
||||||
@ -664,27 +664,39 @@ func (b *backend) Put(r *http.Request) (*internal.Href, error) {
|
|||||||
|
|
||||||
t, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
t, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, internal.HTTPErrorf(http.StatusBadRequest, "carddav: malformed Content-Type: %v", err)
|
return internal.HTTPErrorf(http.StatusBadRequest, "carddav: malformed Content-Type: %v", err)
|
||||||
}
|
}
|
||||||
if t != vcard.MIMEType {
|
if t != vcard.MIMEType {
|
||||||
// TODO: send CARDDAV:supported-address-data error
|
// TODO: send CARDDAV:supported-address-data error
|
||||||
return nil, internal.HTTPErrorf(http.StatusBadRequest, "carddav: unsupporetd Content-Type %q", t)
|
return internal.HTTPErrorf(http.StatusBadRequest, "carddav: unsupporetd Content-Type %q", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check CARDDAV:max-resource-size precondition
|
// TODO: check CARDDAV:max-resource-size precondition
|
||||||
card, err := vcard.NewDecoder(r.Body).Decode()
|
card, err := vcard.NewDecoder(r.Body).Decode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: send CARDDAV:valid-address-data error
|
// TODO: send CARDDAV:valid-address-data error
|
||||||
return nil, internal.HTTPErrorf(http.StatusBadRequest, "carddav: failed to parse vCard: %v", err)
|
return internal.HTTPErrorf(http.StatusBadRequest, "carddav: failed to parse vCard: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add support for the CARDDAV:no-uid-conflict error
|
// TODO: add support for the CARDDAV:no-uid-conflict error
|
||||||
loc, err := b.Backend.PutAddressObject(r.Context(), r.URL.Path, card, &opts)
|
ao, err := b.Backend.PutAddressObject(r.Context(), r.URL.Path, card, &opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
|
}
|
||||||
|
if ao.ETag != "" {
|
||||||
|
w.Header().Set("ETag", internal.ETag(ao.ETag).String())
|
||||||
|
}
|
||||||
|
if !ao.ModTime.IsZero() {
|
||||||
|
w.Header().Set("Last-Modified", ao.ModTime.UTC().Format(http.TimeFormat))
|
||||||
|
}
|
||||||
|
if ao.Path != "" {
|
||||||
|
w.Header().Set("Location", ao.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &internal.Href{Path: loc}, nil
|
// TODO: http.StatusNoContent if the resource already existed
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *backend) Delete(r *http.Request) error {
|
func (b *backend) Delete(r *http.Request) error {
|
||||||
@ -731,7 +743,7 @@ func (b *backend) Move(r *http.Request, dest *internal.Href, overwrite bool) (cr
|
|||||||
return false, internal.HTTPErrorf(http.StatusNotImplemented, "carddav: Move not implemented")
|
return false, internal.HTTPErrorf(http.StatusNotImplemented, "carddav: Move not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://tools.ietf.org/rfcmarkup?doc=6352#section-6.3.2.1
|
// PreconditionType as defined in https://tools.ietf.org/rfcmarkup?doc=6352#section-6.3.2.1
|
||||||
type PreconditionType string
|
type PreconditionType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -742,7 +754,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func NewPreconditionError(err PreconditionType) error {
|
func NewPreconditionError(err PreconditionType) error {
|
||||||
name := xml.Name{"urn:ietf:params:xml:ns:carddav", string(err)}
|
name := xml.Name{Space: "urn:ietf:params:xml:ns:carddav", Local: string(err)}
|
||||||
elem := internal.NewRawXMLElement(name, nil, nil)
|
elem := internal.NewRawXMLElement(name, nil, nil)
|
||||||
return &internal.HTTPError{
|
return &internal.HTTPError{
|
||||||
Code: 409,
|
Code: 409,
|
||||||
|
@ -66,7 +66,7 @@ type Backend interface {
|
|||||||
HeadGet(w http.ResponseWriter, r *http.Request) error
|
HeadGet(w http.ResponseWriter, r *http.Request) error
|
||||||
PropFind(r *http.Request, pf *PropFind, depth Depth) (*MultiStatus, error)
|
PropFind(r *http.Request, pf *PropFind, depth Depth) (*MultiStatus, error)
|
||||||
PropPatch(r *http.Request, pu *PropertyUpdate) (*Response, error)
|
PropPatch(r *http.Request, pu *PropertyUpdate) (*Response, error)
|
||||||
Put(r *http.Request) (*Href, error)
|
Put(w http.ResponseWriter, r *http.Request) error
|
||||||
Delete(r *http.Request) error
|
Delete(r *http.Request) error
|
||||||
Mkcol(r *http.Request) error
|
Mkcol(r *http.Request) error
|
||||||
Copy(r *http.Request, dest *Href, recursive, overwrite bool) (created bool, err error)
|
Copy(r *http.Request, dest *Href, recursive, overwrite bool) (created bool, err error)
|
||||||
@ -88,17 +88,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
case http.MethodGet, http.MethodHead:
|
case http.MethodGet, http.MethodHead:
|
||||||
err = h.Backend.HeadGet(w, r)
|
err = h.Backend.HeadGet(w, r)
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
var href *Href
|
err = h.Backend.Put(w, r)
|
||||||
href, err = h.Backend.Put(r)
|
|
||||||
if err == nil {
|
|
||||||
// TODO: Last-Modified, ETag, Content-Type if the request has
|
|
||||||
// been copied verbatim
|
|
||||||
if href != nil {
|
|
||||||
w.Header().Set("Location", (*url.URL)(href).String())
|
|
||||||
}
|
|
||||||
// TODO: http.StatusNoContent if the resource already existed
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
|
||||||
}
|
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
// TODO: send a multistatus in case of partial failure
|
// TODO: send a multistatus in case of partial failure
|
||||||
err = h.Backend.Delete(r)
|
err = h.Backend.Delete(r)
|
||||||
|
17
server.go
17
server.go
@ -38,7 +38,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b := backend{h.FileSystem}
|
b := backend{h.FileSystem}
|
||||||
hh := internal.Handler{&b}
|
hh := internal.Handler{Backend: &b}
|
||||||
hh.ServeHTTP(w, r)
|
hh.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,18 +193,25 @@ func (b *backend) PropPatch(r *http.Request, update *internal.PropertyUpdate) (*
|
|||||||
return nil, internal.HTTPErrorf(http.StatusForbidden, "webdav: PROPPATCH is unsupported")
|
return nil, internal.HTTPErrorf(http.StatusForbidden, "webdav: PROPPATCH is unsupported")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *backend) Put(r *http.Request) (*internal.Href, error) {
|
func (b *backend) Put(w http.ResponseWriter, r *http.Request) error {
|
||||||
wc, err := b.FileSystem.Create(r.Context(), r.URL.Path)
|
wc, err := b.FileSystem.Create(r.Context(), r.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
defer wc.Close()
|
defer wc.Close()
|
||||||
|
|
||||||
if _, err := io.Copy(wc, r.Body); err != nil {
|
if _, err := io.Copy(wc, r.Body); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
|
}
|
||||||
|
if err := wc.Close(); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, wc.Close()
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
// TODO: Last-Modified, ETag, Content-Type if the request has been copied
|
||||||
|
// verbatim
|
||||||
|
// TODO: http.StatusNoContent if the resource already existed
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *backend) Delete(r *http.Request) error {
|
func (b *backend) Delete(r *http.Request) error {
|
||||||
|
Loading…
Reference in New Issue
Block a user