diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index e9bf990319d..700fc0360ea 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -29,7 +29,6 @@ import ( "path" "strconv" "strings" - "time" "go.opencensus.io/trace" @@ -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 @@ -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, @@ -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", ""), - 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", "")) + 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 @@ -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 { @@ -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", "")) @@ -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", "")) @@ -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) { @@ -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 @@ -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, "")) } diff --git a/internal/http/services/owncloud/ocdav/publicfile.go b/internal/http/services/owncloud/ocdav/publicfile.go index cbb3bc944b7..a87e84c8dad 100644 --- a/internal/http/services/owncloud/ocdav/publicfile.go +++ b/internal/http/services/owncloud/ocdav/publicfile.go @@ -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 } @@ -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 { @@ -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 +}