From 1bed621bd61f06dc354b8040ce4774b6a74334ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 28 Jan 2021 22:18:10 +0000 Subject: [PATCH] fix trash href, location and filenamen, correctly escape property values 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 | 94 +++++++++++-------- .../http/services/owncloud/ocdav/trashbin.go | 11 ++- 2 files changed, 61 insertions(+), 44 deletions(-) diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index cc1e59ef8c..af2c5e3bdf 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -280,22 +280,31 @@ func (s *svc) formatPropfind(ctx context.Context, pf *propfindXML, mds []*provid return msg, nil } -func (s *svc) xmlEscaped(val string) string { +func (s *svc) xmlEscaped(val string) []byte { buf := new(bytes.Buffer) xml.Escape(buf, []byte(val)) - return buf.String() + return buf.Bytes() } func (s *svc) newPropNS(namespace string, local string, val string) *propertyXML { return &propertyXML{ XMLName: xml.Name{Space: namespace, Local: local}, Lang: "", - InnerXML: []byte(val), + InnerXML: s.xmlEscaped(val), } } // TODO properly use the space func (s *svc) newProp(key, val string) *propertyXML { + return &propertyXML{ + XMLName: xml.Name{Space: "", Local: key}, + Lang: "", + InnerXML: s.xmlEscaped(val), + } +} + +// TODO properly use the space +func (s *svc) newPropRaw(key, val string) *propertyXML { return &propertyXML{ XMLName: xml.Name{Space: "", Local: key}, Lang: "", @@ -345,17 +354,21 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide sublog.Debug().Interface("role", role).Str("dav-permissions", wdp).Msg("converted PermissionSet") } + propstatOK := propstatXML{ + Status: "HTTP/1.1 200 OK", + Prop: []*propertyXML{}, + } + propstatNotFound := propstatXML{ + Status: "HTTP/1.1 404 Not Found", + Prop: []*propertyXML{}, + } // when allprops has been requested if pf.Allprop != nil { // return all known properties - response.Propstat = append(response.Propstat, propstatXML{ - Status: "HTTP/1.1 200 OK", - Prop: []*propertyXML{}, - }) if md.Id != nil { id := wrapResourceID(md.Id) - response.Propstat[0].Prop = append(response.Propstat[0].Prop, + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:id", id), s.newProp("oc:fileid", id), ) @@ -365,27 +378,30 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // 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/' - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("d:getetag", md.Etag)) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getetag", md.Etag)) } if md.PermissionSet != nil { - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:permissions", wdp)) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:permissions", wdp)) } // always return size size := fmt.Sprintf("%d", md.Size) if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - response.Propstat[0].Prop = append(response.Propstat[0].Prop, - s.newProp("d:resourcetype", ""), - s.newProp("d:getcontenttype", "httpd/unix-directory"), + propstatOK.Prop = append(propstatOK.Prop, + s.newPropRaw("d:resourcetype", ""), s.newProp("oc:size", size), ) + propstatNotFound.Prop = append(propstatNotFound.Prop, + s.newProp("d:getcontenttype", ""), + s.newProp("d:getcontentlength", ""), + ) } else { - response.Propstat[0].Prop = append(response.Propstat[0].Prop, + propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontentlength", size), ) if md.MimeType != "" { - response.Propstat[0].Prop = append(response.Propstat[0].Prop, + propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontenttype", md.MimeType), ) } @@ -393,7 +409,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // Finder needs the getLastModified property to work. t := utils.TSToTime(md.Mtime).UTC() lastModifiedString := t.Format(time.RFC1123Z) - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("d:getlastmodified", lastModifiedString)) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getlastmodified", lastModifiedString)) // stay bug compatible with oc10, see https://github.com/owncloud/core/pull/38304#issuecomment-762185241 var checksums strings.Builder @@ -423,30 +439,22 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } if checksums.Len() > 0 { checksums.WriteString("") - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:checksums", checksums.String())) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:checksums", checksums.String())) } // favorites from arbitrary metadata if k := md.GetArbitraryMetadata(); k == nil { - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.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")) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) } else if v, ok := amd[_propOcFavorite]; ok && v != "" { - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:favorite", v)) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", v)) } else { - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) } // TODO return other properties ... but how do we put them in a namespace? } else { // otherwise return only the requested properties - propstatOK := propstatXML{ - Status: "HTTP/1.1 200 OK", - Prop: []*propertyXML{}, - } - propstatNotFound := propstatXML{ - Status: "HTTP/1.1 404 Not Found", - Prop: []*propertyXML{}, - } size := fmt.Sprintf("%d", md.Size) for i := range pf.Prop { switch pf.Prop[i].Space { @@ -538,7 +546,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide if md.Owner != nil { if isCurrentUserOwner(ctx, md.Owner) { u := ctxuser.ContextMustGetUser(ctx) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:owner-id", s.xmlEscaped(u.Username))) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:owner-id", u.Username)) } else { sublog.Debug().Msg("TODO fetch user username") propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-id", "")) @@ -652,14 +660,15 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } case "resourcetype": // both if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("d:resourcetype", "")) } else { propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:resourcetype", "")) // redirectref is another option } case "getcontenttype": // phoenix if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontenttype", "httpd/unix-directory")) + // directories have no contenttype + propstatNotFound.Prop = append(propstatOK.Prop, s.newProp("d:getcontenttype", "")) } else if md.MimeType != "" { propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontenttype", md.MimeType)) } @@ -706,12 +715,13 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } } } - if len(propstatOK.Prop) > 0 { - response.Propstat = append(response.Propstat, propstatOK) - } - if len(propstatNotFound.Prop) > 0 { - response.Propstat = append(response.Propstat, propstatNotFound) - } + } + + if len(propstatOK.Prop) > 0 { + response.Propstat = append(response.Propstat, propstatOK) + } + if len(propstatNotFound.Prop) > 0 { + response.Propstat = append(response.Propstat, propstatNotFound) } return &response, nil @@ -819,7 +829,7 @@ type propertyXML struct { Lang string `xml:"xml:lang,attr,omitempty"` // InnerXML contains the XML representation of the property value. - // See http://www.ocwebdav.org/specs/rfc4918.html#property_values + // See http://www.webdav.org/specs/rfc4918.html#property_values // // Property values of complex type or mixed-content must have fully // expanded XML namespaces or be self-contained with according @@ -828,3 +838,9 @@ type propertyXML struct { // even including the DAV: namespace. InnerXML []byte `xml:",innerxml"` } + +// satisfies io.Writer interface +func (p *propertyXML) Write(b []byte) (n int, err error) { + p.InnerXML = append(p.InnerXML, b...) + return len(b), nil +} diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index 0f71df9da2..23c258c230 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -24,6 +24,7 @@ import ( "fmt" "net/http" "path" + "path/filepath" "strings" "time" @@ -223,7 +224,7 @@ func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, u *us }) for i := range items { - res, err := h.itemToPropResponse(ctx, s, pf, items[i]) + res, err := h.itemToPropResponse(ctx, s, u, pf, items[i]) if err != nil { return "", err } @@ -243,10 +244,10 @@ func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, u *us // itemToPropResponse needs to create a listing that contains a key and destination // the key is the name of an entry in the trash listing // for now we need to limit trash to the users home, so we can expect all trash keys to have the home storage as the opaque id -func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, pf *propfindXML, item *provider.RecycleItem) (*responseXML, error) { +func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, u *userpb.User, pf *propfindXML, item *provider.RecycleItem) (*responseXML, error) { baseURI := ctx.Value(ctxKeyBaseURI).(string) - ref := path.Join(baseURI, item.Key) + ref := path.Join(baseURI, u.Username, item.Key) if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { ref += "/" } @@ -269,7 +270,7 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, pf *pr Prop: []*propertyXML{}, }) // yes this is redundant, can be derived from oc:trashbin-original-location which contains the full path, clients should not fetch it - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-original-filename", strings.TrimPrefix(item.Path, "/"))) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-original-filename", filepath.Base(item.Path))) response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-original-location", strings.TrimPrefix(item.Path, "/"))) response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-delete-datetime", dTime)) if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { @@ -305,7 +306,7 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, pf *pr } 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, s.newProp("oc:trashbin-original-filename", strings.TrimPrefix(item.Path, "/"))) + propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-original-filename", filepath.Base(item.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, s.newProp("oc:trashbin-original-location", strings.TrimPrefix(item.Path, "/")))