Skip to content

Commit

Permalink
fix trash href, location and filenamen, correctly escape property values
Browse files Browse the repository at this point in the history
Signed-off-by: Jörn Friedrich Dreyer <[email protected]>
  • Loading branch information
butonic committed Jan 28, 2021
1 parent 221f445 commit 1bed621
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 44 deletions.
94 changes: 55 additions & 39 deletions internal/http/services/owncloud/ocdav/propfind.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: "",
Expand Down Expand Up @@ -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),
)
Expand All @@ -365,35 +378,38 @@ 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", "<d:collection/>"),
s.newProp("d:getcontenttype", "httpd/unix-directory"),
propstatOK.Prop = append(propstatOK.Prop,
s.newPropRaw("d:resourcetype", "<d:collection/>"),
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),
)
}
}
// 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
Expand Down Expand Up @@ -423,30 +439,22 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide
}
if checksums.Len() > 0 {
checksums.WriteString("</oc:checksum>")
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 {
Expand Down Expand Up @@ -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", ""))
Expand Down Expand Up @@ -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", "<d:collection/>"))
propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("d:resourcetype", "<d:collection/>"))
} 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))
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
}
11 changes: 6 additions & 5 deletions internal/http/services/owncloud/ocdav/trashbin.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"fmt"
"net/http"
"path"
"path/filepath"
"strings"
"time"

Expand Down Expand Up @@ -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
}
Expand All @@ -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 += "/"
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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, "/")))
Expand Down

0 comments on commit 1bed621

Please sign in to comment.