Skip to content

Commit

Permalink
align public file link root collection rendering
Browse files Browse the repository at this point in the history
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
  • Loading branch information
butonic committed Jan 29, 2021
1 parent 6554e2c commit 08d5c13
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 68 deletions.
99 changes: 60 additions & 39 deletions internal/http/services/owncloud/ocdav/propfind.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"path"
"strconv"
"strings"
"time"

"go.opencensus.io/trace"

Expand All @@ -50,6 +49,9 @@ const (
_nsOCS = "http://open-collaboration-services.org/ns"

_propOcFavorite = "http://owncloud.org/ns/favorite"

// RFC1123 time that mimics oc10. time.RFC1123 would end in "UTC", see https://github.com/golang/go/issues/13781
RFC1123 = "Mon, 02 Jan 2006 15:04:05 GMT"
)

// ns is the namespace that is prefixed to the path in the cs3 namespace
Expand Down Expand Up @@ -344,7 +346,11 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide

isShared := !isCurrentUserOwner(ctx, md.Owner)
var wdp string
if md.PermissionSet != nil {
switch {
case ls != nil:
// link share only appears on root collection
wdp = ""
case md.PermissionSet != nil:
wdp = role.WebDAVPermissions(
md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER,
isShared,
Expand Down Expand Up @@ -385,32 +391,30 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide
propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:permissions", wdp))
}

// always return size
// always return size, well nearly always ... public link shares are a little weird
size := fmt.Sprintf("%d", md.Size)
if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER {
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", ""),
)
propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("d:resourcetype", "<d:collection/>"))
if ls == nil {
propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:size", size))
} else {
//no size, but also no 404
}
} else {
propstatOK.Prop = append(propstatOK.Prop,
s.newProp("d:resourcetype", ""),
s.newProp("d:getcontentlength", size),
)
if md.MimeType != "" {
propstatOK.Prop = append(propstatOK.Prop,
s.newProp("d:getcontenttype", md.MimeType),
)
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)
propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getlastmodified", lastModifiedString))
if md.Mtime != nil {
t := utils.TSToTime(md.Mtime).UTC()
lastModifiedString := t.Format(RFC1123)
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 @@ -443,15 +447,18 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide
propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("oc:checksums", checksums.String()))
}

// favorites from arbitrary metadata
if k := md.GetArbitraryMetadata(); k == nil {
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[_propOcFavorite]; ok && v != "" {
propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", v))
} else {
propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0"))
// 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, 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[_propOcFavorite]; ok && v != "" {
propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", v))
} else {
propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0"))
}
}
// TODO return other properties ... but how do we put them in a namespace?
} else {
Expand Down Expand Up @@ -512,7 +519,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide
case "public-link-share-datetime":
if ls != nil && ls.Mtime != nil {
t := utils.TSToTime(ls.Mtime).UTC() // TODO or ctime?
shareTimeString := t.Format(time.RFC1123Z)
shareTimeString := t.Format(RFC1123)
propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-share-datetime", shareTimeString))
} else {
propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-share-datetime", ""))
Expand All @@ -533,7 +540,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide
case "public-link-expiration":
if ls != nil && ls.Expiration != nil {
t := utils.TSToTime(ls.Expiration).UTC()
expireTimeString := t.Format(time.RFC1123Z)
expireTimeString := t.Format(RFC1123)
propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-expiration", expireTimeString))
} else {
propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-expiration", ""))
Expand All @@ -542,7 +549,12 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide
case "size": // phoenix only
// TODO we cannot find out if md.Size is set or not because ints in go default to 0
// oc:size is also available on folders
propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:size", size))
if ls == nil {
propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:size", size))
} else {
// link share root collection has no size
propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:size", ""))
}
case "owner-id": // phoenix only
if md.Owner != nil {
if isCurrentUserOwner(ctx, md.Owner) {
Expand All @@ -559,14 +571,19 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide
// TODO: can be 0 or 1?, in oc10 it is present or not
// TODO: read favorite via separate call? that would be expensive? I hope it is in the md
// 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 k := md.GetArbitraryMetadata(); k == nil {
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[_propOcFavorite]; ok && v != "" {
propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "1"))
if ls == nil {
if k := md.GetArbitraryMetadata(); k == nil {
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[_propOcFavorite]; ok && v != "" {
propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "1"))
} else {
propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0"))
}
} else {
propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0"))
// link share root collection has no favorite
propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:favorite", ""))
}
case "checksums": // desktop ... not really ... the desktop sends the OC-Checksum header

Expand Down Expand Up @@ -675,9 +692,13 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide
}
case "getlastmodified": // both
// TODO we cannot find out if md.Mtime is set or not because ints in go default to 0
t := utils.TSToTime(md.Mtime).UTC()
lastModifiedString := t.Format(time.RFC1123Z)
propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getlastmodified", lastModifiedString))
if md.Mtime != nil {
t := utils.TSToTime(md.Mtime).UTC()
lastModifiedString := t.Format(RFC1123)
propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getlastmodified", lastModifiedString))
} else {
propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getlastmodified", ""))
}
default:
propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:"+pf.Prop[i].Local, ""))
}
Expand Down
72 changes: 43 additions & 29 deletions internal/http/services/owncloud/ocdav/publicfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ import (

rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/rhttp/router"
"go.opencensus.io/trace"
)

// PublicFileHandler handles trashbin requests
// PublicFileHandler handles requests on a shared file. it needs to be wrapped in a collection
type PublicFileHandler struct {
namespace string
}
Expand Down Expand Up @@ -177,39 +178,15 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s
return
}

infos := []*provider.ResourceInfo{}

if onContainer {
// TODO: filter out metadata like favorite and arbitrary metadata
if depth != "0" {
// if the request is to a public link, we need to add yet another value for the file entry.
infos = append(infos, &provider.ResourceInfo{
// append the shared as a container. Annex to OC10 standards.
Id: tokenStatInfo.Id,
Path: tokenStatInfo.Path,
Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER,
Mtime: tokenStatInfo.Mtime,
Size: tokenStatInfo.Size,
Etag: tokenStatInfo.Etag,
PermissionSet: tokenStatInfo.PermissionSet,
})
}
} else if path.Base(r.URL.Path) != path.Base(pathRes.Path) {
if !onContainer && path.Base(r.URL.Path) != path.Base(pathRes.Path) {
// if queried on the wrong path, return not found
w.WriteHeader(http.StatusNotFound)
return
}
// adjust path
tokenStatInfo.Path = path.Join("/", tokenStatInfo.Path, path.Base(pathRes.Path))

infos = append(infos, &provider.ResourceInfo{
Id: tokenStatInfo.Id,
Path: path.Join("/", tokenStatInfo.Path, path.Base(pathRes.Path)),
Type: tokenStatInfo.Type,
Size: tokenStatInfo.Size,
MimeType: tokenStatInfo.MimeType,
Mtime: tokenStatInfo.Mtime,
Etag: tokenStatInfo.Etag,
PermissionSet: tokenStatInfo.PermissionSet,
})
infos := s.getPublicFileInfos(onContainer, depth == "0", tokenStatInfo)

propRes, err := s.formatPropfind(ctx, &pf, infos, ns)
if err != nil {
Expand All @@ -225,3 +202,40 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s
sublog.Err(err).Msg("error writing response")
}
}

// there are only two possible entries
// 1. the non existing collection
// 2. the shared file
func (s *svc) getPublicFileInfos(onContainer, onlyRoot bool, i *provider.ResourceInfo) []*provider.ResourceInfo {
infos := []*provider.ResourceInfo{}
if onContainer {
// copy link-share data if present
// we don't copy everything because the checksum should not be present
var o *typesv1beta1.Opaque
if i.Opaque != nil && i.Opaque.Map != nil && i.Opaque.Map["link-share"] != nil {
o = &typesv1beta1.Opaque{
Map: map[string]*typesv1beta1.OpaqueEntry{
"link-share": i.Opaque.Map["link-share"],
},
}
}
// always add collection
infos = append(infos, &provider.ResourceInfo{
// Opaque carries the link-share data we need when rendering the collection root href
Opaque: o,
Path: path.Dir(i.Path),
Type: provider.ResourceType_RESOURCE_TYPE_CONTAINER,
})
if onlyRoot {
return infos
}
}

// link share only appears on root collection
delete(i.Opaque.Map, "link-share")

// add the file info
infos = append(infos, i)

return infos
}

0 comments on commit 08d5c13

Please sign in to comment.