From 081fe441e4d13d8c9cb43aee645095f90215ae3f Mon Sep 17 00:00:00 2001 From: David Christofas Date: Tue, 5 Apr 2022 12:49:13 +0200 Subject: [PATCH] Ocdav improvements (#2658) * use different props method for static values When putting static values in propstat properties we can skip the xml encoding when we know that there are no reserved characters. Same for 'not found' properties. * reduce unnecessary use of pointers * simplify the EncodePath method The results of the old code were a bit different but this version is still compliant to the webdav rfc: https://datatracker.ietf.org/doc/html/rfc4918#section-8.3.1. It is also a whole lot faster. * add changelog * improve naming of prop package * update core commit id --- .drone.env | 2 +- changelog/unreleased/ocdav-cleanup.md | 5 + .../http/services/owncloud/ocdav/locks.go | 6 +- .../http/services/owncloud/ocdav/net/net.go | 30 +-- .../services/owncloud/ocdav/net/net_test.go | 2 +- .../ocdav/{props/props.go => prop/prop.go} | 36 +++- .../owncloud/ocdav/propfind/propfind.go | 200 +++++++++--------- .../http/services/owncloud/ocdav/proppatch.go | 18 +- .../http/services/owncloud/ocdav/trashbin.go | 70 +++--- .../owncloud/ocs/conversions/permissions.go | 5 + 10 files changed, 186 insertions(+), 188 deletions(-) create mode 100644 changelog/unreleased/ocdav-cleanup.md rename internal/http/services/owncloud/ocdav/{props/props.go => prop/prop.go} (80%) diff --git a/.drone.env b/.drone.env index fd04cf6687..15c8d1ed6b 100644 --- a/.drone.env +++ b/.drone.env @@ -1,3 +1,3 @@ # The test runner source for API tests -CORE_COMMITID=e285879a8a79e692497937ebf340bc6b9c925b4f +CORE_COMMITID=cb558615d4a1850629be1c463454dabac71a4905 CORE_BRANCH=master diff --git a/changelog/unreleased/ocdav-cleanup.md b/changelog/unreleased/ocdav-cleanup.md new file mode 100644 index 0000000000..ecbe7e69d3 --- /dev/null +++ b/changelog/unreleased/ocdav-cleanup.md @@ -0,0 +1,5 @@ +Change: small clean up of the ocdav code + +Cleaned up the ocdav code to make it more readable and in one case a bit faster. + +https://github.com/cs3org/reva/pull/2658 diff --git a/internal/http/services/owncloud/ocdav/locks.go b/internal/http/services/owncloud/ocdav/locks.go index e4bfb00446..822d23dc94 100644 --- a/internal/http/services/owncloud/ocdav/locks.go +++ b/internal/http/services/owncloud/ocdav/locks.go @@ -36,7 +36,7 @@ import ( types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/props" + "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/prop" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/v2/pkg/appctx" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" @@ -557,10 +557,10 @@ func writeLockInfo(w io.Writer, token string, ld LockDetails) (int, error) { lockdiscovery.WriteString(" Infinite\n") } if token != "" { - lockdiscovery.WriteString(fmt.Sprintf(" %s\n", props.Escape(token))) + lockdiscovery.WriteString(fmt.Sprintf(" %s\n", prop.Escape(token))) } if href != "" { - lockdiscovery.WriteString(fmt.Sprintf(" %s\n", props.Escape(href))) + lockdiscovery.WriteString(fmt.Sprintf(" %s\n", prop.Escape(href))) } lockdiscovery.WriteString("") diff --git a/internal/http/services/owncloud/ocdav/net/net.go b/internal/http/services/owncloud/ocdav/net/net.go index 586c8c7cc4..9fab05c963 100644 --- a/internal/http/services/owncloud/ocdav/net/net.go +++ b/internal/http/services/owncloud/ocdav/net/net.go @@ -19,9 +19,7 @@ package net import ( - "fmt" "net/url" - "regexp" "strings" "github.com/pkg/errors" @@ -69,37 +67,11 @@ func (d Depth) String() string { return string(d) } -// replaceAllStringSubmatchFunc is taken from 'Go: Replace String with Regular Expression Callback' -// see: https://elliotchance.medium.com/go-replace-string-with-regular-expression-callback-f89948bad0bb -func replaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string { - result := "" - lastIndex := 0 - for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) { - groups := []string{} - for i := 0; i < len(v); i += 2 { - groups = append(groups, str[v[i]:v[i+1]]) - } - result += str[lastIndex:v[0]] + repl(groups) - lastIndex = v[1] - } - return result + str[lastIndex:] -} - -var hrefre = regexp.MustCompile(`([^A-Za-z0-9_\-.~()/:@!$])`) - // EncodePath encodes the path of a url. // // slashes (/) are treated as path-separators. -// ported from https://github.com/sabre-io/http/blob/bb27d1a8c92217b34e778ee09dcf79d9a2936e84/lib/functions.php#L369-L379 func EncodePath(path string) string { - return replaceAllStringSubmatchFunc(hrefre, path, func(groups []string) string { - b := groups[1] - var sb strings.Builder - for i := 0; i < len(b); i++ { - sb.WriteString(fmt.Sprintf("%%%x", b[i])) - } - return sb.String() - }) + return (&url.URL{Path: path}).EscapedPath() } // ParseDepth parses the depth header value defined in https://tools.ietf.org/html/rfc4918#section-9.1 diff --git a/internal/http/services/owncloud/ocdav/net/net_test.go b/internal/http/services/owncloud/ocdav/net/net_test.go index 24ee8da430..3b254067f3 100644 --- a/internal/http/services/owncloud/ocdav/net/net_test.go +++ b/internal/http/services/owncloud/ocdav/net/net_test.go @@ -64,7 +64,7 @@ var _ = Describe("Net", func() { Describe("EncodePath", func() { It("encodes paths", func() { Expect(net.EncodePath("foo")).To(Equal("foo")) - Expect(net.EncodePath("/some/path/Folder %^*(#1)")).To(Equal("/some/path/Folder%20%25%5e%2a(%231)")) + Expect(net.EncodePath("/some/path/Folder %^*(#1)")).To(Equal("/some/path/Folder%20%25%5E%2A%28%231%29")) }) /* diff --git a/internal/http/services/owncloud/ocdav/props/props.go b/internal/http/services/owncloud/ocdav/prop/prop.go similarity index 80% rename from internal/http/services/owncloud/ocdav/props/props.go rename to internal/http/services/owncloud/ocdav/prop/prop.go index f3065e357c..607679520c 100644 --- a/internal/http/services/owncloud/ocdav/props/props.go +++ b/internal/http/services/owncloud/ocdav/prop/prop.go @@ -16,7 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package props +package prop import ( "bytes" @@ -49,29 +49,45 @@ func xmlEscaped(val string) []byte { return buf.Bytes() } -// NewPropNS returns a new PropertyXML instance -func NewPropNS(namespace string, local string, val string) *PropertyXML { - return &PropertyXML{ +// EscapedNS returns a new PropertyXML instance while xml-escaping the value +func EscapedNS(namespace string, local string, val string) PropertyXML { + return PropertyXML{ XMLName: xml.Name{Space: namespace, Local: local}, Lang: "", InnerXML: xmlEscaped(val), } } -// NewProp returns a new PropertyXML instance while xml-escaping the value +// Escaped returns a new PropertyXML instance while xml-escaping the value // TODO properly use the space -func NewProp(key, val string) *PropertyXML { - return &PropertyXML{ +func Escaped(key, val string) PropertyXML { + return PropertyXML{ XMLName: xml.Name{Space: "", Local: key}, Lang: "", InnerXML: xmlEscaped(val), } } -// NewPropRaw returns a new PropertyXML instance for the given key/value pair +// NotFound returns a new PropertyXML instance with an empty value +func NotFound(key string) PropertyXML { + return PropertyXML{ + XMLName: xml.Name{Space: "", Local: key}, + Lang: "", + } +} + +// NotFoundNS returns a new PropertyXML instance with the given namespace and an empty value +func NotFoundNS(namespace, key string) PropertyXML { + return PropertyXML{ + XMLName: xml.Name{Space: namespace, Local: key}, + Lang: "", + } +} + +// Raw returns a new PropertyXML instance for the given key/value pair // TODO properly use the space -func NewPropRaw(key, val string) *PropertyXML { - return &PropertyXML{ +func Raw(key, val string) PropertyXML { + return PropertyXML{ XMLName: xml.Name{Space: "", Local: key}, Lang: "", InnerXML: []byte(val), diff --git a/internal/http/services/owncloud/ocdav/propfind/propfind.go b/internal/http/services/owncloud/ocdav/propfind/propfind.go index 7e84424c64..a5e52c2eb4 100644 --- a/internal/http/services/owncloud/ocdav/propfind/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind/propfind.go @@ -39,7 +39,7 @@ import ( "github.com/cs3org/reva/v2/internal/grpc/services/storageprovider" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/props" + "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/prop" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/v2/pkg/appctx" @@ -87,10 +87,10 @@ type PropstatXML struct { // not honoring namespace declarations inside a xmltag with a // parent element for anonymous slice elements. // Use of multistatusWriter takes care of this. - Prop []*props.PropertyXML `xml:"d:prop>_ignored_"` - Status string `xml:"d:status"` - Error *errors.ErrorXML `xml:"d:error"` - ResponseDescription string `xml:"d:responsedescription,omitempty"` + Prop []prop.PropertyXML `xml:"d:prop>_ignored_"` + Status string `xml:"d:status"` + Error *errors.ErrorXML `xml:"d:error"` + ResponseDescription string `xml:"d:responsedescription,omitempty"` } // ResponseXML holds the xml representation of a propfind response @@ -140,10 +140,10 @@ type PropstatUnmarshalXML struct { // not honoring namespace declarations inside a xmltag with a // parent element for anonymous slice elements. // Use of multistatusWriter takes care of this. - Prop []*props.PropertyXML `xml:"prop"` - Status string `xml:"status"` - Error *errors.ErrorXML `xml:"d:error"` - ResponseDescription string `xml:"responsedescription,omitempty"` + Prop []*prop.PropertyXML `xml:"prop"` + Status string `xml:"status"` + Error *errors.ErrorXML `xml:"d:error"` + ResponseDescription string `xml:"responsedescription,omitempty"` } // NewMultiStatusResponseXML returns a preconfigured instance of MultiStatusResponseXML @@ -732,11 +732,11 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p propstatOK := PropstatXML{ Status: "HTTP/1.1 200 OK", - Prop: []*props.PropertyXML{}, + Prop: []prop.PropertyXML{}, } propstatNotFound := PropstatXML{ Status: "HTTP/1.1 404 Not Found", - Prop: []*props.PropertyXML{}, + Prop: []prop.PropertyXML{}, } // when allprops has been requested if pf.Allprop != nil { @@ -745,9 +745,9 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p if md.Id != nil { id := resourceid.OwnCloudResourceIDWrap(md.Id) propstatOK.Prop = append(propstatOK.Prop, - props.NewProp("oc:id", id), - props.NewProp("oc:fileid", id), - props.NewProp("oc:spaceid", md.Id.StorageId), + prop.Escaped("oc:id", id), + prop.Escaped("oc:fileid", id), + prop.Escaped("oc:spaceid", md.Id.StorageId), ) } @@ -755,37 +755,37 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p // etags must be enclosed in double quotes and cannot contain them. // See https://tools.ietf.org/html/rfc7232#section-2.3 for details // TODO(jfd) handle weak tags that start with 'W/' - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getetag", quoteEtag(md.Etag))) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getetag", quoteEtag(md.Etag))) } if md.PermissionSet != nil { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:permissions", wdp)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:permissions", wdp)) } // always return size, well nearly always ... public link shares are a little weird if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("d:resourcetype", "")) if ls == nil { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:size", size)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:size", size)) } // A PROPFIND request SHOULD NOT return DAV:quota-available-bytes and DAV:quota-used-bytes // from https://www.rfc-editor.org/rfc/rfc4331.html#section-2 - // propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:quota-used-bytes", size)) - // propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:quota-available-bytes", quota)) + // propstatOK.Prop = append(propstatOK.Prop, prop.NewProp("d:quota-used-bytes", size)) + // propstatOK.Prop = append(propstatOK.Prop, prop.NewProp("d:quota-available-bytes", quota)) } else { propstatOK.Prop = append(propstatOK.Prop, - props.NewProp("d:resourcetype", ""), - props.NewProp("d:getcontentlength", size), + prop.Escaped("d:resourcetype", ""), + prop.Escaped("d:getcontentlength", size), ) if md.MimeType != "" { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontenttype", md.MimeType)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getcontenttype", md.MimeType)) } } // Finder needs the getLastModified property to work. if md.Mtime != nil { t := utils.TSToTime(md.Mtime).UTC() lastModifiedString := t.Format(net.RFC1123) - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getlastmodified", lastModifiedString)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getlastmodified", lastModifiedString)) } // stay bug compatible with oc10, see https://github.com/owncloud/core/pull/38304#issuecomment-762185241 @@ -816,25 +816,25 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p } if checksums.Len() > 0 { checksums.WriteString("") - propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("oc:checksums", checksums.String())) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("oc:checksums", checksums.String())) } // ls do not report any properties as missing by default if ls == nil { // favorites from arbitrary metadata if k := md.GetArbitraryMetadata(); k == nil { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("oc:favorite", "0")) } else if amd := k.GetMetadata(); amd == nil { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("oc:favorite", "0")) } else if v, ok := amd[net.PropOcFavorite]; ok && v != "" { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", v)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:favorite", v)) } else { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("oc:favorite", "0")) } } if lock != nil { - propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("d:lockdiscovery", activeLocks(&sublog, lock))) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("d:lockdiscovery", activeLocks(&sublog, lock))) } // TODO return other properties ... but how do we put them in a namespace? } else { @@ -847,21 +847,21 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p // I tested the desktop client and phoenix to annotate which properties are requestted, see below cases case "fileid": // phoenix only if md.Id != nil { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:fileid", resourceid.OwnCloudResourceIDWrap(md.Id))) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:fileid", resourceid.OwnCloudResourceIDWrap(md.Id))) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:fileid", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:fileid")) } case "id": // desktop client only if md.Id != nil { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:id", resourceid.OwnCloudResourceIDWrap(md.Id))) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:id", resourceid.OwnCloudResourceIDWrap(md.Id))) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:id", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:id")) } case "spaceid": if md.Id != nil { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:spaceid", md.Id.StorageId)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:spaceid", md.Id.StorageId)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:spaceid", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.Escaped("oc:spaceid", "")) } case "permissions": // both // oc:permissions take several char flags to indicate the permissions the user has on this node: @@ -873,78 +873,78 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p // R = Shareable (Reshare) // M = Mounted // in contrast, the ocs:share-permissions further down below indicate clients the maximum permissions that can be granted - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:permissions", wdp)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:permissions", wdp)) case "public-link-permission": // only on a share root node if ls != nil && md.PermissionSet != nil { propstatOK.Prop = append( propstatOK.Prop, - props.NewProp("oc:public-link-permission", strconv.FormatUint(uint64(role.OCSPermissions()), 10))) + prop.Escaped("oc:public-link-permission", role.OCSPermissions().String())) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-permission", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:public-link-permission")) } case "public-link-item-type": // only on a share root node if ls != nil { if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:public-link-item-type", "folder")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("oc:public-link-item-type", "folder")) } else { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:public-link-item-type", "file")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("oc:public-link-item-type", "file")) // redirectref is another option } } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-item-type", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:public-link-item-type")) } case "public-link-share-datetime": if ls != nil && ls.Mtime != nil { t := utils.TSToTime(ls.Mtime).UTC() // TODO or ctime? shareTimeString := t.Format(net.RFC1123) - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:public-link-share-datetime", shareTimeString)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:public-link-share-datetime", shareTimeString)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-share-datetime", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:public-link-share-datetime")) } case "public-link-share-owner": if ls != nil && ls.Owner != nil { if net.IsCurrentUserOwner(ctx, ls.Owner) { u := ctxpkg.ContextMustGetUser(ctx) - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:public-link-share-owner", u.Username)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:public-link-share-owner", u.Username)) } else { u, _ := ctxpkg.ContextGetUser(ctx) sublog.Error().Interface("share", ls).Interface("user", u).Msg("the current user in the context should be the owner of a public link share") - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-share-owner", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:public-link-share-owner")) } } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-share-owner", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:public-link-share-owner")) } case "public-link-expiration": if ls != nil && ls.Expiration != nil { t := utils.TSToTime(ls.Expiration).UTC() expireTimeString := t.Format(net.RFC1123) - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:public-link-expiration", expireTimeString)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:public-link-expiration", expireTimeString)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-expiration", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:public-link-expiration")) } - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-expiration", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:public-link-expiration")) case "size": // phoenix only // TODO we cannot find out if md.Size is set or not because ints in go default to 0 // TODO what is the difference to d:quota-used-bytes (which only exists for collections)? // oc:size is available on files and folders and behaves like d:getcontentlength or d:quota-used-bytes respectively // The hasPrefix is a workaround to make children of the link root show a size if they have 0 bytes if ls == nil || strings.HasPrefix(md.Path, "/"+ls.Token+"/") { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:size", size)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:size", size)) } else { // link share root collection has no size - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:size", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:size")) } case "owner-id": // phoenix only if md.Owner != nil { if net.IsCurrentUserOwner(ctx, md.Owner) { u := ctxpkg.ContextMustGetUser(ctx) - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:owner-id", u.Username)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:owner-id", u.Username)) } else { sublog.Debug().Msg("TODO fetch user username") - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:owner-id", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:owner-id")) } } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:owner-id", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:owner-id")) } case "favorite": // phoenix only // TODO: can be 0 or 1?, in oc10 it is present or not @@ -952,17 +952,17 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p // TODO: this boolean favorite property is so horribly wrong ... either it is presont, or it is not ... unless ... it is possible to have a non binary value ... we need to double check if ls == nil { if k := md.GetArbitraryMetadata(); k == nil { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("oc:favorite", "0")) } else if amd := k.GetMetadata(); amd == nil { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("oc:favorite", "0")) } else if v, ok := amd[net.PropOcFavorite]; ok && v != "" { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "1")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("oc:favorite", "1")) } else { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("oc:favorite", "0")) } } else { // link share root collection has no favorite - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:favorite", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:favorite")) } case "checksums": // desktop ... not really ... the desktop sends the OC-Checksum header @@ -994,9 +994,9 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p } if checksums.Len() > 13 { checksums.WriteString("") - propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("oc:checksums", checksums.String())) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("oc:checksums", checksums.String())) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:checksums", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:checksums")) } case "share-types": // desktop var types strings.Builder @@ -1015,21 +1015,21 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p } if types.Len() != 0 { - propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("oc:share-types", types.String())) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("oc:share-types", types.String())) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:"+pf.Prop[i].Local)) } case "owner-display-name": // phoenix only if md.Owner != nil { if net.IsCurrentUserOwner(ctx, md.Owner) { u := ctxpkg.ContextMustGetUser(ctx) - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:owner-display-name", u.DisplayName)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:owner-display-name", u.DisplayName)) } else { sublog.Debug().Msg("TODO fetch user displayname") - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:owner-display-name", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:owner-display-name")) } } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:owner-display-name", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:owner-display-name")) } case "downloadURL": // desktop if isPublic && md.Type == provider.ResourceType_RESOURCE_TYPE_FILE { @@ -1048,9 +1048,9 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p path = sb.String() } - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:downloadURL", publicURL+baseURI+path)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:downloadURL", publicURL+baseURI+path)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:"+pf.Prop[i].Local)) } case "signature-auth": if isPublic { @@ -1065,9 +1065,9 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p sb.WriteString(expiration.Format(time.RFC3339)) sb.WriteString("") - propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("oc:signature-auth", sb.String())) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("oc:signature-auth", sb.String())) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:signature-auth", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:signature-auth")) } } case "privatelink": // phoenix only @@ -1083,15 +1083,15 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p // TODO(jfd): double check the client behavior with reva on backup restore fallthrough default: - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:"+pf.Prop[i].Local)) } case net.NsDav: switch pf.Prop[i].Local { case "getetag": // both if md.Etag != "" { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getetag", quoteEtag(md.Etag))) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getetag", quoteEtag(md.Etag))) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getetag", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("d:getetag")) } case "getcontentlength": // both // see everts stance on this https://stackoverflow.com/a/31621912, he points to http://tools.ietf.org/html/rfc4918#section-15.3 @@ -1100,57 +1100,57 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p // which is not the case ... so we don't return it on collections. owncloud has oc:size for that // TODO we cannot find out if md.Size is set or not because ints in go default to 0 if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getcontentlength", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("d:getcontentlength")) } else { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontentlength", size)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getcontentlength", size)) } case "resourcetype": // both if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("d:resourcetype", "")) } else { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("d:resourcetype", "")) // redirectref is another option } case "getcontenttype": // phoenix if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { // directories have no contenttype - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getcontenttype", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("d:getcontenttype")) } else if md.MimeType != "" { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontenttype", md.MimeType)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getcontenttype", md.MimeType)) } case "getlastmodified": // both // TODO we cannot find out if md.Mtime is set or not because ints in go default to 0 if md.Mtime != nil { t := utils.TSToTime(md.Mtime).UTC() lastModifiedString := t.Format(net.RFC1123) - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getlastmodified", lastModifiedString)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getlastmodified", lastModifiedString)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getlastmodified", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("d:getlastmodified")) } case "quota-used-bytes": // RFC 4331 if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { // always returns the current usage, // in oc10 there seems to be a bug that makes the size in webdav differ from the one in the user properties, not taking shares into account // in ocis we plan to always mak the quota a property of the storage space - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:quota-used-bytes", size)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:quota-used-bytes", size)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:quota-used-bytes", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("d:quota-used-bytes")) } case "quota-available-bytes": // RFC 4331 if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { // oc10 returns -3 for unlimited, -2 for unknown, -1 for uncalculated - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:quota-available-bytes", quota)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:quota-available-bytes", quota)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:quota-available-bytes", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("d:quota-available-bytes")) } case "lockdiscovery": // http://www.webdav.org/specs/rfc2518.html#PROPERTY_lockdiscovery if lock == nil { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:lockdiscovery", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("d:lockdiscovery")) } else { - propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("d:lockdiscovery", activeLocks(&sublog, lock))) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("d:lockdiscovery", activeLocks(&sublog, lock))) } default: - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("d:"+pf.Prop[i].Local)) } case net.NsOCS: switch pf.Prop[i].Local { @@ -1169,21 +1169,21 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p perms &^= conversions.PermissionCreate perms &^= conversions.PermissionDelete } - propstatOK.Prop = append(propstatOK.Prop, props.NewPropNS(pf.Prop[i].Space, pf.Prop[i].Local, strconv.FormatUint(uint64(perms), 10))) + propstatOK.Prop = append(propstatOK.Prop, prop.EscapedNS(pf.Prop[i].Space, pf.Prop[i].Local, perms.String())) } default: - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("d:"+pf.Prop[i].Local)) } default: // handle custom properties if k := md.GetArbitraryMetadata(); k == nil { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFoundNS(pf.Prop[i].Space, pf.Prop[i].Local)) } else if amd := k.GetMetadata(); amd == nil { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFoundNS(pf.Prop[i].Space, pf.Prop[i].Local)) } else if v, ok := amd[metadataKeyOf(&pf.Prop[i])]; ok && v != "" { - propstatOK.Prop = append(propstatOK.Prop, props.NewPropNS(pf.Prop[i].Space, pf.Prop[i].Local, v)) + propstatOK.Prop = append(propstatOK.Prop, prop.EscapedNS(pf.Prop[i].Space, pf.Prop[i].Local, v)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFoundNS(pf.Prop[i].Space, pf.Prop[i].Local)) } } } @@ -1248,13 +1248,13 @@ func activeLocks(log *zerolog.Logger, lock *provider.Lock) string { if lock.User != nil { // TODO oc10 uses displayname and email, needs a user lookup - activelocks.WriteString(props.Escape(lock.User.OpaqueId + "@" + lock.User.Idp)) + activelocks.WriteString(prop.Escape(lock.User.OpaqueId + "@" + lock.User.Idp)) } if lock.AppName != "" { if lock.User != nil { activelocks.WriteString(" via ") } - activelocks.WriteString(props.Escape(lock.AppName)) + activelocks.WriteString(prop.Escape(lock.AppName)) } activelocks.WriteString("") } @@ -1263,7 +1263,7 @@ func activeLocks(log *zerolog.Logger, lock *provider.Lock) string { activelocks.WriteString("") if lock.LockId != "" { activelocks.WriteString("") - activelocks.WriteString(props.Escape(lock.LockId)) + activelocks.WriteString(prop.Escape(lock.LockId)) activelocks.WriteString("") } // lockroot is only used when setting the lock @@ -1300,7 +1300,7 @@ func metadataKeyOf(n *xml.Name) string { // properties contain values. Character data between properties is ignored. func (pn *Props) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { for { - t, err := props.Next(d) + t, err := prop.Next(d) if err != nil { return err } @@ -1314,7 +1314,7 @@ func (pn *Props) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { */ return nil case xml.StartElement: - t, err = props.Next(d) + t, err = prop.Next(d) if err != nil { return err } diff --git a/internal/http/services/owncloud/ocdav/proppatch.go b/internal/http/services/owncloud/ocdav/proppatch.go index 8d28bb2e13..950ea76abd 100644 --- a/internal/http/services/owncloud/ocdav/proppatch.go +++ b/internal/http/services/owncloud/ocdav/proppatch.go @@ -32,8 +32,8 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/prop" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/props" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/v2/pkg/appctx" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" @@ -323,9 +323,9 @@ func (s *svc) formatProppatchResponse(ctx context.Context, acceptedProps []xml.N } if len(acceptedProps) > 0 { - propstatBody := []*props.PropertyXML{} + propstatBody := []prop.PropertyXML{} for i := range acceptedProps { - propstatBody = append(propstatBody, props.NewPropNS(acceptedProps[i].Space, acceptedProps[i].Local, "")) + propstatBody = append(propstatBody, prop.EscapedNS(acceptedProps[i].Space, acceptedProps[i].Local, "")) } response.Propstat = append(response.Propstat, propfind.PropstatXML{ Status: "HTTP/1.1 200 OK", @@ -334,9 +334,9 @@ func (s *svc) formatProppatchResponse(ctx context.Context, acceptedProps []xml.N } if len(removedProps) > 0 { - propstatBody := []*props.PropertyXML{} + propstatBody := []prop.PropertyXML{} for i := range removedProps { - propstatBody = append(propstatBody, props.NewPropNS(removedProps[i].Space, removedProps[i].Local, "")) + propstatBody = append(propstatBody, prop.EscapedNS(removedProps[i].Space, removedProps[i].Local, "")) } response.Propstat = append(response.Propstat, propfind.PropstatXML{ Status: "HTTP/1.1 204 No Content", @@ -386,11 +386,11 @@ type Proppatch struct { // remove them, it sets them. Remove bool // Props contains the properties to be set or removed. - Props []props.PropertyXML + Props []prop.PropertyXML } // http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for proppatch) -type proppatchProps []props.PropertyXML +type proppatchProps []prop.PropertyXML // UnmarshalXML appends the property names and values enclosed within start // to ps. @@ -403,7 +403,7 @@ type proppatchProps []props.PropertyXML func (ps *proppatchProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { lang := xmlLang(start, "") for { - t, err := props.Next(d) + t, err := prop.Next(d) if err != nil { return err } @@ -414,7 +414,7 @@ func (ps *proppatchProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) e } return nil case xml.StartElement: - p := props.PropertyXML{} + p := prop.PropertyXML{} err = d.DecodeElement(&p, &elem) if err != nil { return err diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index b94ee5f0d7..158acfe6ec 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -31,8 +31,8 @@ import ( "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/prop" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind" - "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/props" "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup" rtrace "github.com/cs3org/reva/v2/pkg/trace" "github.com/cs3org/reva/v2/pkg/utils/resourceid" @@ -299,17 +299,17 @@ func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, space Propstat: []propfind.PropstatXML{ { Status: "HTTP/1.1 200 OK", - Prop: []*props.PropertyXML{ - props.NewPropRaw("d:resourcetype", ""), + Prop: []prop.PropertyXML{ + prop.Raw("d:resourcetype", ""), }, }, { Status: "HTTP/1.1 404 Not Found", - Prop: []*props.PropertyXML{ - props.NewProp("oc:trashbin-original-filename", ""), - props.NewProp("oc:trashbin-original-location", ""), - props.NewProp("oc:trashbin-delete-datetime", ""), - props.NewProp("d:getcontentlength", ""), + Prop: []prop.PropertyXML{ + prop.NotFound("oc:trashbin-original-filename"), + prop.NotFound("oc:trashbin-original-location"), + prop.NotFound("oc:trashbin-delete-datetime"), + prop.NotFound("d:getcontentlength"), }, }, }, @@ -362,33 +362,33 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, spaceI // return all known properties propstatOK := propfind.PropstatXML{ Status: "HTTP/1.1 200 OK", - Prop: []*props.PropertyXML{}, + Prop: []prop.PropertyXML{}, } // yes this is redundant, can be derived from oc:trashbin-original-location which contains the full path, clients should not fetch it - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-original-filename", path.Base(item.Ref.Path))) - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-delete-datetime", dTime)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:trashbin-original-filename", path.Base(item.Ref.Path))) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:trashbin-delete-datetime", dTime)) if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("d:resourcetype", "")) // TODO(jfd): decide if we can and want to list oc:size for folders } else { propstatOK.Prop = append(propstatOK.Prop, - props.NewProp("d:resourcetype", ""), - props.NewProp("d:getcontentlength", size), + prop.Escaped("d:resourcetype", ""), + prop.Escaped("d:getcontentlength", size), ) } - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:spaceid", spaceID)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:spaceid", spaceID)) response.Propstat = append(response.Propstat, propstatOK) } else { // otherwise return only the requested properties propstatOK := propfind.PropstatXML{ Status: "HTTP/1.1 200 OK", - Prop: []*props.PropertyXML{}, + Prop: []prop.PropertyXML{}, } propstatNotFound := propfind.PropstatXML{ Status: "HTTP/1.1 404 Not Found", - Prop: []*props.PropertyXML{}, + Prop: []prop.PropertyXML{}, } for i := range pf.Prop { switch pf.Prop[i].Space { @@ -396,52 +396,52 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, spaceI switch pf.Prop[i].Local { case "oc:size": if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontentlength", size)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getcontentlength", size)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:size", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:size")) } case "trashbin-original-filename": // yes this is redundant, can be derived from oc:trashbin-original-location which contains the full path, clients should not fetch it - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-original-filename", path.Base(item.Ref.Path))) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:trashbin-original-filename", path.Base(item.Ref.Path))) case "trashbin-original-location": // TODO (jfd) double check and clarify the cs3 spec what the Key is about and if Path is only the folder that contains the file or if it includes the filename - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) case "trashbin-delete-datetime": - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-delete-datetime", dTime)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:trashbin-delete-datetime", dTime)) case "trashbin-delete-timestamp": - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) case "spaceid": - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:spaceid", spaceID)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:spaceid", spaceID)) default: - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("oc:"+pf.Prop[i].Local)) } case net.NsDav: switch pf.Prop[i].Local { case "getcontentlength": if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getcontentlength", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("d:getcontentlength")) } else { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontentlength", size)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getcontentlength", size)) } case "resourcetype": if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("d:resourcetype", "")) } else { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("d:resourcetype", "")) // redirectref is another option } case "getcontenttype": if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontenttype", "httpd/unix-directory")) + propstatOK.Prop = append(propstatOK.Prop, prop.Raw("d:getcontenttype", "httpd/unix-directory")) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getcontenttype", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("d:getcontenttype")) } default: - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound("d:"+pf.Prop[i].Local)) } default: // TODO (jfd) lookup shortname for unknown namespaces? - propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp(pf.Prop[i].Space+":"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, prop.NotFound(pf.Prop[i].Space+":"+pf.Prop[i].Local)) } } response.Propstat = append(response.Propstat, propstatOK, propstatNotFound) diff --git a/internal/http/services/owncloud/ocs/conversions/permissions.go b/internal/http/services/owncloud/ocs/conversions/permissions.go index b3cc3a2eb4..bc2f86e0e5 100644 --- a/internal/http/services/owncloud/ocs/conversions/permissions.go +++ b/internal/http/services/owncloud/ocs/conversions/permissions.go @@ -20,6 +20,7 @@ package conversions import ( "fmt" + "strconv" ) // Permissions reflects the CRUD permissions used in the OCS sharing API @@ -62,3 +63,7 @@ func NewPermissions(val int) (Permissions, error) { func (p Permissions) Contain(other Permissions) bool { return p&other == other } + +func (p Permissions) String() string { + return strconv.FormatUint(uint64(p), 10) +}