From 2ab71a99e0635df4f940f513b8241e49045efc15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Mon, 11 Jan 2021 11:12:32 +0000 Subject: [PATCH] request unknown properties as arbitrary metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../http/services/owncloud/ocdav/propfind.go | 60 ++++++++++++++++--- .../http/services/owncloud/ocdav/proppatch.go | 6 +- .../http/services/owncloud/ocdav/trashbin.go | 4 +- pkg/storage/fs/ocis/ocis.go | 5 +- 4 files changed, 59 insertions(+), 16 deletions(-) diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index b0c17af2110..69c25eaeaff 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -45,6 +45,15 @@ import ( "github.com/pkg/errors" ) +const ( + _nsDav = "DAV:" + _nsOwncloud = "http://owncloud.org/ns" + _nsOCS = "http://open-collaboration-services.org/ns" + + _propOcFavorite = "http://owncloud.org/ns/favorite" + _propOcShareTypes = "http://owncloud.org/ns/share-types" +) + // ns is the namespace that is prefixed to the path in the cs3 namespace func (s *svc) handlePropfind(w http.ResponseWriter, r *http.Request, ns string) { ctx := r.Context() @@ -98,13 +107,24 @@ func (s *svc) handlePropfind(w http.ResponseWriter, r *http.Request, ns string) return } + metadataKeys := []string{} + if pf.Allprop != nil { + metadataKeys = append(metadataKeys, "*") + } else { + for i := range pf.Prop { + if requiresExplicitFetching(&pf.Prop[i]) { + metadataKeys = append(metadataKeys, metadataKeyOf(&pf.Prop[i])) + } + } + } + info := res.Info infos := []*provider.ResourceInfo{info} if info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER && depth == "1" { req := &provider.ListContainerRequest{ Ref: ref, ArbitraryMetadataKeys: []string{ - "http://owncloud.org/ns/share-types", + _propOcShareTypes, }, } res, err := client.ListContainer(ctx, req) @@ -132,7 +152,7 @@ func (s *svc) handlePropfind(w http.ResponseWriter, r *http.Request, ns string) req := &provider.ListContainerRequest{ Ref: ref, ArbitraryMetadataKeys: []string{ - "http://owncloud.org/ns/share-types", + _propOcShareTypes, }, } res, err := client.ListContainer(ctx, req) @@ -188,6 +208,23 @@ func (s *svc) handlePropfind(w http.ResponseWriter, r *http.Request, ns string) } } +func requiresExplicitFetching(n *xml.Name) bool { + switch n.Space { + case _nsDav: + return false + case _nsOwncloud: + switch n.Local { + case "favorite", "share-types": + return true + default: + return false + } + case _nsOCS: + return false + } + return true +} + // from https://github.com/golang/net/blob/e514e69ffb8bc3c76a71ae40de0118d794855992/webdav/xml.go#L178-L205 func readPropfind(r io.Reader) (pf propfindXML, status int, err error) { c := countingReader{r: r} @@ -253,6 +290,7 @@ func (s *svc) newPropNS(namespace string, local string, val string) *propertyXML } } +// TODO properly use the space func (s *svc) newProp(key, val string) *propertyXML { return &propertyXML{ XMLName: xml.Name{Space: "", Local: key}, @@ -368,7 +406,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:favorite", "0")) } else if amd := k.GetMetadata(); amd == nil { response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:favorite", "0")) - } else if v, ok := amd["http://owncloud.org/ns/favorite"]; ok && v != "" { + } else if v, ok := amd[_propOcFavorite]; ok && v != "" { response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:favorite", "1")) } else { response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:favorite", "0")) @@ -387,7 +425,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide size := fmt.Sprintf("%d", md.Size) for i := range pf.Prop { switch pf.Prop[i].Space { - case "http://owncloud.org/ns": + case _nsOwncloud: switch pf.Prop[i].Local { // TODO(jfd): maybe phoenix and the other clients can just use this id as an opaque string? // I tested the desktop client and phoenix to annotate which properties are requestted, see below cases @@ -491,7 +529,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) } else if amd := k.GetMetadata(); amd == nil { propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) - } else if v, ok := amd["http://owncloud.org/ns/favorite"]; ok && v != "" { + } else if v, ok := amd[_propOcFavorite]; ok && v != "" { propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "1")) } else { propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) @@ -511,7 +549,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide case "share-types": // desktop k := md.GetArbitraryMetadata() amd := k.GetMetadata() - if amdv, ok := amd[fmt.Sprintf("%s/%s", pf.Prop[i].Space, pf.Prop[i].Local)]; ok { + if amdv, ok := amd[metadataKeyOf(&pf.Prop[i])]; ok { st := fmt.Sprintf("%s", amdv) propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:share-types", st)) } else { @@ -546,7 +584,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide default: propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, "")) } - case "DAV:": + case _nsDav: switch pf.Prop[i].Local { case "getetag": // both if md.Etag != "" { @@ -586,7 +624,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide default: propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:"+pf.Prop[i].Local, "")) } - case "http://open-collaboration-services.org/ns": + case _nsOCS: switch pf.Prop[i].Local { // ocs:share-permissions indicate clients the maximum permissions that can be granted: // 1 = read @@ -614,7 +652,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide propstatNotFound.Prop = append(propstatNotFound.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) } else if amd := k.GetMetadata(); amd == nil { propstatNotFound.Prop = append(propstatNotFound.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) - } else if v, ok := amd[fmt.Sprintf("%s/%s", pf.Prop[i].Space, pf.Prop[i].Local)]; ok && v != "" { + } else if v, ok := amd[metadataKeyOf(&pf.Prop[i])]; ok && v != "" { propstatOK.Prop = append(propstatOK.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, v)) } else { propstatNotFound.Prop = append(propstatNotFound.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) @@ -654,6 +692,10 @@ func (c *countingReader) Read(p []byte) (int, error) { return n, err } +func metadataKeyOf(n *xml.Name) string { + return fmt.Sprintf("%s/%s", n.Space, n.Local) +} + // http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind) type propfindProps []xml.Name diff --git a/internal/http/services/owncloud/ocdav/proppatch.go b/internal/http/services/owncloud/ocdav/proppatch.go index 218bcd5c0f7..b0be5ab3257 100644 --- a/internal/http/services/owncloud/ocdav/proppatch.go +++ b/internal/http/services/owncloud/ocdav/proppatch.go @@ -218,7 +218,7 @@ func (s *svc) formatProppatchResponse(ctx context.Context, acceptedProps []xml.N func (s *svc) isBooleanProperty(prop string) bool { // TODO add other properties we know to be boolean? - return prop == "http://owncloud.org/ns/favorite" + return prop == _propOcFavorite } func (s *svc) as0or1(val string) string { @@ -307,9 +307,9 @@ func readProppatch(r io.Reader) (patches []Proppatch, status int, err error) { for _, op := range pu.SetRemove { remove := false switch op.XMLName { - case xml.Name{Space: "DAV:", Local: "set"}: + case xml.Name{Space: _nsDav, Local: "set"}: // No-op. - case xml.Name{Space: "DAV:", Local: "remove"}: + case xml.Name{Space: _nsDav, Local: "remove"}: for _, p := range op.Prop { if len(p.InnerXML) > 0 { return nil, http.StatusBadRequest, errInvalidProppatch diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index e931bef3124..4fa4e3dc1de 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -295,7 +295,7 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, pf *pr size := fmt.Sprintf("%d", item.Size) for i := range pf.Prop { switch pf.Prop[i].Space { - case "http://owncloud.org/ns": + case _nsOwncloud: switch pf.Prop[i].Local { case "oc:size": if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { @@ -314,7 +314,7 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, pf *pr default: propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, "")) } - case "DAV:": + case _nsDav: switch pf.Prop[i].Local { case "getcontentlength": if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { diff --git a/pkg/storage/fs/ocis/ocis.go b/pkg/storage/fs/ocis/ocis.go index 3e59a70bf3c..e1f8e7fd79d 100644 --- a/pkg/storage/fs/ocis/ocis.go +++ b/pkg/storage/fs/ocis/ocis.go @@ -62,8 +62,9 @@ const ( // grantPrefix is the prefix for sharing related extended attributes grantPrefix string = ocisPrefix + "grant." metadataPrefix string = ocisPrefix + "md." - // TODO implement favorites metadata flag - favPrefix string = ocisPrefix + "fav." // favorite flag, per user + + // favorite flag, per user + favPrefix string = ocisPrefix + "fav." // a temporary etag for a folder that is removed when the mtime propagation happens tmpEtagAttr string = ocisPrefix + "tmp.etag"