Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ocdav: Adjust propstat response for removed properties #742

Merged
merged 2 commits into from
May 15, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 113 additions & 65 deletions internal/http/services/owncloud/ocdav/proppatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
package ocdav

import (
"context"
"encoding/xml"
"fmt"
"io"
"net/http"
"net/url"
"path"
"strings"

Expand All @@ -40,6 +42,9 @@ func (s *svc) handleProppatch(w http.ResponseWriter, r *http.Request, ns string)
defer span.End()
log := appctx.GetLogger(ctx)

acceptedProps := []xml.Name{}
removedProps := []xml.Name{}

ns = applyLayout(ctx, ns)

fn := path.Join(ns, r.URL.Path)
Expand All @@ -58,16 +63,34 @@ func (s *svc) handleProppatch(w http.ResponseWriter, r *http.Request, ns string)
return
}

mkeys := []string{}
// check if resource exists
statReq := &provider.StatRequest{
Ref: &provider.Reference{
Spec: &provider.Reference_Path{Path: fn},
},
}
statRes, err := c.Stat(ctx, statReq)
if err != nil {
log.Error().Err(err).Msg("error sending a grpc stat request")
w.WriteHeader(http.StatusInternalServerError)
return
}

pf := &propfindXML{
Prop: propfindProps{},
if statRes.Status.Code != rpc.Code_CODE_OK {
if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND {
log.Warn().Str("path", fn).Msg("resource not found")
w.WriteHeader(http.StatusNotFound)
return
}
w.WriteHeader(http.StatusInternalServerError)
return
}

rreq := &provider.UnsetArbitraryMetadataRequest{
Ref: &provider.Reference{
Spec: &provider.Reference_Path{Path: fn},
},
ArbitraryMetadataKeys: []string{},
ArbitraryMetadataKeys: []string{""},
}
sreq := &provider.SetArbitraryMetadataRequest{
Ref: &provider.Reference{
Expand All @@ -82,7 +105,7 @@ func (s *svc) handleProppatch(w http.ResponseWriter, r *http.Request, ns string)
continue
}
for j := range pp[i].Props {
pf.Prop = append(pf.Prop, pp[i].Props[j].XMLName)
propNameXML := pp[i].Props[j].XMLName
// don't use path.Join. It removes the double slash! concatenate with a /
key := fmt.Sprintf("%s/%s", pp[i].Props[j].XMLName.Space, pp[i].Props[j].XMLName.Local)
value := string(pp[i].Props[j].InnerXML)
Expand All @@ -95,81 +118,65 @@ func (s *svc) handleProppatch(w http.ResponseWriter, r *http.Request, ns string)
remove = true
}
}
// Webdav spec requires the operations to be executed in the order
// specified in the PROPPATCH request
// http://www.webdav.org/specs/rfc2518.html#rfc.section.8.2
// FIXME: batch this somehow
if remove {
rreq.ArbitraryMetadataKeys = append(rreq.ArbitraryMetadataKeys, key)
rreq.ArbitraryMetadataKeys[0] = key
res, err := c.UnsetArbitraryMetadata(ctx, rreq)
if err != nil {
log.Error().Err(err).Msg("error sending a grpc UnsetArbitraryMetadata request")
w.WriteHeader(http.StatusInternalServerError)
return
}

if res.Status.Code != rpc.Code_CODE_OK {
if res.Status.Code == rpc.Code_CODE_NOT_FOUND {
log.Warn().Str("path", fn).Msg("resource not found")
w.WriteHeader(http.StatusNotFound)
return
}
w.WriteHeader(http.StatusInternalServerError)
return
}
removedProps = append(removedProps, propNameXML)
} else {
sreq.ArbitraryMetadata.Metadata[key] = value
}
mkeys = append(mkeys, key)
}
// what do we need to unset
if len(rreq.ArbitraryMetadataKeys) > 0 {
res, err := c.UnsetArbitraryMetadata(ctx, rreq)
if err != nil {
log.Error().Err(err).Msg("error sending a grpc UnsetArbitraryMetadata request")
w.WriteHeader(http.StatusInternalServerError)
return
}

if res.Status.Code != rpc.Code_CODE_OK {
if res.Status.Code == rpc.Code_CODE_NOT_FOUND {
log.Warn().Str("path", fn).Msg("resource not found")
w.WriteHeader(http.StatusNotFound)
res, err := c.SetArbitraryMetadata(ctx, sreq)
if err != nil {
log.Error().Err(err).Str("key", key).Str("value", value).Msg("error sending a grpc SetArbitraryMetadata request")
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusInternalServerError)
return
}
}
if len(sreq.ArbitraryMetadata.Metadata) > 0 {
res, err := c.SetArbitraryMetadata(ctx, sreq)
if err != nil {
log.Error().Err(err).Msg("error sending a grpc SetArbitraryMetadata request")
w.WriteHeader(http.StatusInternalServerError)
return
}

if res.Status.Code != rpc.Code_CODE_OK {
if res.Status.Code == rpc.Code_CODE_NOT_FOUND {
log.Warn().Str("path", fn).Msg("resource not found")
w.WriteHeader(http.StatusNotFound)
if res.Status.Code != rpc.Code_CODE_OK {
if res.Status.Code == rpc.Code_CODE_NOT_FOUND {
log.Warn().Str("path", fn).Msg("resource not found")
w.WriteHeader(http.StatusNotFound)
return
}
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusInternalServerError)
return
acceptedProps = append(acceptedProps, propNameXML)
delete(sreq.ArbitraryMetadata.Metadata, key)
}
}
// FIXME: in case of error, need to set all properties back to the original state,
// and return the error in the matching propstat block, if applicable
// http://www.webdav.org/specs/rfc2518.html#rfc.section.8.2
}

req := &provider.StatRequest{
Ref: &provider.Reference{
Spec: &provider.Reference_Path{Path: fn},
},
ArbitraryMetadataKeys: mkeys,
}
res, err := c.Stat(ctx, req)
if err != nil {
log.Error().Err(err).Msg("error sending a grpc stat request")
w.WriteHeader(http.StatusInternalServerError)
return
}

if res.Status.Code != rpc.Code_CODE_OK {
if res.Status.Code == rpc.Code_CODE_NOT_FOUND {
log.Warn().Str("path", fn).Msg("resource not found")
w.WriteHeader(http.StatusNotFound)
return
}
w.WriteHeader(http.StatusInternalServerError)
return
ref := strings.TrimPrefix(fn, ns)
ref = path.Join(ctx.Value(ctxKeyBaseURI).(string), ref)
if statRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER {
ref += "/"
}

info := res.Info
infos := []*provider.ResourceInfo{info}

propRes, err := s.formatPropfind(ctx, pf, infos, ns)
propRes, err := s.formatProppatchResponse(ctx, acceptedProps, removedProps, ref)
if err != nil {
log.Error().Err(err).Msg("error formatting propfind")
log.Error().Err(err).Msg("error formatting proppatch response")
w.WriteHeader(http.StatusInternalServerError)
return
}
Expand All @@ -181,6 +188,47 @@ func (s *svc) handleProppatch(w http.ResponseWriter, r *http.Request, ns string)
}
}

func (s *svc) formatProppatchResponse(ctx context.Context, acceptedProps []xml.Name, removedProps []xml.Name, ref string) (string, error) {
responses := make([]responseXML, 0, 1)
response := responseXML{
Href: (&url.URL{Path: ref}).EscapedPath(), // url encode response.Href
Propstat: []propstatXML{},
}

if len(acceptedProps) > 0 {
propstatBody := []*propertyXML{}
for i := range acceptedProps {
propstatBody = append(propstatBody, s.newPropNS(acceptedProps[i].Space, acceptedProps[i].Local, ""))
}
response.Propstat = append(response.Propstat, propstatXML{
Status: "HTTP/1.1 200 OK",
Prop: propstatBody,
})
}

if len(removedProps) > 0 {
propstatBody := []*propertyXML{}
for i := range removedProps {
propstatBody = append(propstatBody, s.newPropNS(removedProps[i].Space, removedProps[i].Local, ""))
}
response.Propstat = append(response.Propstat, propstatXML{
Status: "HTTP/1.1 204 No Content",
Prop: propstatBody,
})
}

responses = append(responses, response)
responsesXML, err := xml.Marshal(&responses)
if err != nil {
return "", err
}

msg := `<?xml version="1.0" encoding="utf-8"?><d:multistatus xmlns:d="DAV:" `
msg += `xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">`
msg += string(responsesXML) + `</d:multistatus>`
return msg, nil
}

func (s *svc) isBooleanProperty(prop string) bool {
// TODO add other properties we know to be boolean?
return prop == "http://owncloud.org/ns/favorite"
Expand Down