bump reva to v2.26.5

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
Jörn Friedrich Dreyer
2024-11-12 15:29:27 +01:00
parent 80861d3a01
commit fe65742adb
23 changed files with 409 additions and 172 deletions

View File

@@ -1,5 +1,9 @@
Bugfix: Bump Reva
Bugfix: Bump Reva to v2.26.5
bumps reva version
* Fix [cs3org/reva#4926](https://github.com/cs3org/reva/issues/4926): Make etag always match content on downloads
* Fix [cs3org/reva#4920](https://github.com/cs3org/reva/issues/4920): Return correct status codes for simple uploads
* Fix [cs3org/reva#4924](https://github.com/cs3org/reva/issues/4924): Fix sync propagation
* Fix [cs3org/reva#4916](https://github.com/cs3org/reva/issues/4916): Improve posixfs stability and performanc
https://github.com/owncloud/ocis/pull/10552
https://github.com/owncloud/ocis/pull/10539

2
go.mod
View File

@@ -16,7 +16,7 @@ require (
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/coreos/go-oidc/v3 v3.11.0
github.com/cs3org/go-cs3apis v0.0.0-20241105092511-3ad35d174fc1
github.com/cs3org/reva/v2 v2.26.5-0.20241111162950-e77dd61e7edb
github.com/cs3org/reva/v2 v2.26.5
github.com/davidbyttow/govips/v2 v2.15.0
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e

4
go.sum
View File

@@ -255,8 +255,8 @@ github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c=
github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
github.com/cs3org/go-cs3apis v0.0.0-20241105092511-3ad35d174fc1 h1:RU6LT6mkD16xZs011+8foU7T3LrPvTTSWeTQ9OgfhkA=
github.com/cs3org/go-cs3apis v0.0.0-20241105092511-3ad35d174fc1/go.mod h1:DedpcqXl193qF/08Y04IO0PpxyyMu8+GrkD6kWK2MEQ=
github.com/cs3org/reva/v2 v2.26.5-0.20241111162950-e77dd61e7edb h1:owRv9x5GlKKdqCCM70kZKCsLAcDkFPkyOb129Jmklt0=
github.com/cs3org/reva/v2 v2.26.5-0.20241111162950-e77dd61e7edb/go.mod h1:KP0Zomt3dNIr/kU2M1mXzTIVFOtxBVS4qmBDMRCfrOQ=
github.com/cs3org/reva/v2 v2.26.5 h1:LWIOSpmgoVQDfe9S2renzqqAXorFs6lT+5Vodhr3M68=
github.com/cs3org/reva/v2 v2.26.5/go.mod h1:KP0Zomt3dNIr/kU2M1mXzTIVFOtxBVS4qmBDMRCfrOQ=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=

View File

@@ -28,6 +28,9 @@ import (
"github.com/Masterminds/sprig"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/errtypes"
"github.com/cs3org/reva/v2/pkg/events"
@@ -36,8 +39,6 @@ import (
"github.com/cs3org/reva/v2/pkg/storage/utils/eosfs"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)
func init() {
@@ -193,12 +194,12 @@ func (w *wrapper) ListRevisions(ctx context.Context, ref *provider.Reference) ([
return w.FS.ListRevisions(ctx, ref)
}
func (w *wrapper) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) {
func (w *wrapper) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
if err := w.userIsProjectAdmin(ctx, ref); err != nil {
return nil, err
return nil, nil, err
}
return w.FS.DownloadRevision(ctx, ref, revisionKey)
return w.FS.DownloadRevision(ctx, ref, revisionKey, openReaderfunc)
}
func (w *wrapper) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error {

View File

@@ -38,6 +38,8 @@ import (
ocmpb "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
typepb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/studio-b12/gowebdav"
"github.com/cs3org/reva/v2/pkg/errtypes"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/mime"
@@ -49,7 +51,6 @@ import (
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/cs3org/reva/v2/pkg/utils/cfg"
"github.com/studio-b12/gowebdav"
)
func init() {
@@ -364,13 +365,30 @@ func (d *driver) ListFolder(ctx context.Context, ref *provider.Reference, _ []st
return res, nil
}
func (d *driver) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) {
client, _, rel, err := d.webdavClient(ctx, nil, ref)
func (d *driver) Download(ctx context.Context, ref *provider.Reference, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
client, share, rel, err := d.webdavClient(ctx, nil, ref)
if err != nil {
return nil, err
return nil, nil, err
}
return client.ReadStream(rel)
info, err := client.StatWithProps(rel, []string{}) // request all properties by giving an empty list
if err != nil {
if gowebdav.IsErrNotFound(err) {
return nil, nil, errtypes.NotFound(ref.GetPath())
}
return nil, nil, err
}
md, err := convertStatToResourceInfo(ref, info, share)
if err != nil {
return nil, nil, err
}
if !openReaderfunc(md) {
return md, nil, nil
}
reader, err := client.ReadStream(rel)
return md, reader, err
}
func (d *driver) GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) {
@@ -396,8 +414,8 @@ func (d *driver) ListRevisions(ctx context.Context, ref *provider.Reference) ([]
return nil, errtypes.NotSupported("operation not supported")
}
func (d *driver) DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error) {
return nil, errtypes.NotSupported("operation not supported")
func (d *driver) DownloadRevision(ctx context.Context, ref *provider.Reference, key string, openReaderFunc func(md *provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
return nil, nil, errtypes.NotSupported("operation not supported")
}
func (d *driver) RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error {

View File

@@ -30,6 +30,8 @@ import (
"strings"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/rs/zerolog"
"github.com/cs3org/reva/v2/internal/grpc/services/storageprovider"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
"github.com/cs3org/reva/v2/pkg/appctx"
@@ -37,7 +39,6 @@ import (
"github.com/cs3org/reva/v2/pkg/storage"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/rs/zerolog"
)
type contextKey struct{}
@@ -91,27 +92,47 @@ func GetOrHeadFile(w http.ResponseWriter, r *http.Request, fs storage.FS, spaceI
// TODO check preconditions like If-Range, If-Match ...
var md *provider.ResourceInfo
var content io.ReadCloser
var err error
var notModified bool
// do a stat to set a Content-Length header
// do a stat to set Content-Length and etag headers
if md, err = fs.GetMD(ctx, ref, nil, []string{"size", "mimetype", "etag"}); err != nil {
handleError(w, &sublog, err, "stat")
md, content, err = fs.Download(ctx, ref, func(md *provider.ResourceInfo) bool {
// range requests always need to open the reader to check if it is seekable
if r.Header.Get("Range") != "" {
return true
}
// otherwise, HEAD requests do not need to open a reader
if r.Method == "HEAD" {
return false
}
// check etag, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match
for _, etag := range r.Header.Values(net.HeaderIfNoneMatch) {
if md.Etag == etag {
// When the condition fails for GET and HEAD methods, then the server must return
// HTTP status code 304 (Not Modified). [...] Note that the server generating a
// 304 response MUST generate any of the following header fields that would have
// been sent in a 200 (OK) response to the same request:
// Cache-Control, Content-Location, Date, ETag, Expires, and Vary.
notModified = true
return false
}
}
return true
})
if err != nil {
handleError(w, &sublog, err, "download")
return
}
// check etag, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match
for _, etag := range r.Header.Values(net.HeaderIfNoneMatch) {
if md.Etag == etag {
// When the condition fails for GET and HEAD methods, then the server must return
// HTTP status code 304 (Not Modified). [...] Note that the server generating a
// 304 response MUST generate any of the following header fields that would have
// been sent in a 200 (OK) response to the same request:
// Cache-Control, Content-Location, Date, ETag, Expires, and Vary.
w.Header().Set(net.HeaderETag, md.Etag)
w.WriteHeader(http.StatusNotModified)
return
}
if content != nil {
defer content.Close()
}
if notModified {
w.Header().Set(net.HeaderETag, md.Etag)
w.WriteHeader(http.StatusNotModified)
return
}
// fill in storage provider id if it is missing
@@ -141,14 +162,6 @@ func GetOrHeadFile(w http.ResponseWriter, r *http.Request, fs storage.FS, spaceI
}
}
ctx = ContextWithEtag(ctx, md.Etag)
content, err := fs.Download(ctx, ref)
if err != nil {
handleError(w, &sublog, err, "download")
return
}
defer content.Close()
code := http.StatusOK
sendSize := int64(md.Size)
var sendContent io.Reader = content
@@ -259,6 +272,9 @@ func handleError(w http.ResponseWriter, log *zerolog.Logger, err error, action s
case errtypes.IsPermissionDenied:
log.Debug().Err(err).Str("action", action).Msg("permission denied")
w.WriteHeader(http.StatusForbidden)
case errtypes.Aborted:
log.Debug().Err(err).Str("action", action).Msg("etags do not match")
w.WriteHeader(http.StatusPreconditionFailed)
default:
log.Error().Err(err).Str("action", action).Msg("unexpected error")
w.WriteHeader(http.StatusInternalServerError)

View File

@@ -33,13 +33,14 @@ import (
cephfs2 "github.com/ceph/go-ceph/cephfs"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/cs3org/reva/v2/pkg/appctx"
"github.com/cs3org/reva/v2/pkg/errtypes"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/storage"
"github.com/cs3org/reva/v2/pkg/storage/fs/registry"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)
const (
@@ -276,11 +277,11 @@ func (fs *cephfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKey
return files, getRevaError(err)
}
func (fs *cephfs) Download(ctx context.Context, ref *provider.Reference) (rc io.ReadCloser, err error) {
func (fs *cephfs) Download(ctx context.Context, ref *provider.Reference, openReaderFunc func(md *provider.ResourceInfo) bool) (ri *provider.ResourceInfo, rc io.ReadCloser, err error) {
var path string
user := fs.makeUser(ctx)
if path, err = user.resolveRef(ref); err != nil {
return nil, errors.Wrap(err, "cephfs: error resolving ref")
return nil, nil, errors.Wrap(err, "cephfs: error resolving ref")
}
user.op(func(cv *cacheVal) {
@@ -288,10 +289,24 @@ func (fs *cephfs) Download(ctx context.Context, ref *provider.Reference) (rc io.
err = errtypes.PermissionDenied("cephfs: cannot download under the virtual share folder")
return
}
var stat Statx
if stat, err = cv.mount.Statx(path, cephfs2.StatxBasicStats, 0); err != nil {
return
}
ri, err = user.fileAsResourceInfo(cv, path, stat, nil)
if err != nil {
return
}
if !openReaderFunc(ri) {
return
}
rc, err = cv.mount.Open(path, os.O_RDONLY, 0)
})
return rc, getRevaError(err)
return ri, rc, getRevaError(err)
}
func (fs *cephfs) ListRevisions(ctx context.Context, ref *provider.Reference) (fvs []*provider.FileVersion, err error) {
@@ -341,7 +356,7 @@ func (fs *cephfs) ListRevisions(ctx context.Context, ref *provider.Reference) (f
return fvs, getRevaError(err)
}
func (fs *cephfs) DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (file io.ReadCloser, err error) {
func (fs *cephfs) DownloadRevision(ctx context.Context, ref *provider.Reference, key string, openReaderFunc func(md *provider.ResourceInfo) bool) (ri *provider.ResourceInfo, file io.ReadCloser, err error) {
//TODO(tmourati): Fix entry id logic
user := fs.makeUser(ctx)
@@ -352,10 +367,22 @@ func (fs *cephfs) DownloadRevision(ctx context.Context, ref *provider.Reference,
return
}
var stat Statx
stat, err = cv.mount.Statx(revPath, cephfs2.StatxMtime|cephfs2.StatxSize, 0)
if err != nil {
return
}
ri, err = user.fileAsResourceInfo(cv, revPath, stat, nil)
if err != nil {
return
}
if !openReaderFunc(ri) {
return
}
file, err = cv.mount.Open(revPath, os.O_RDONLY, 0)
})
return file, getRevaError(err)
return ri, file, getRevaError(err)
}
func (fs *cephfs) RestoreRevision(ctx context.Context, ref *provider.Reference, key string) (err error) {

View File

@@ -29,6 +29,7 @@ import (
"time"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/pkg/errtypes"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/storage"
@@ -222,20 +223,23 @@ func (fs *hellofs) ListFolder(ctx context.Context, ref *provider.Reference, mdKe
}
// Download returns a ReadCloser for the content of the referenced resource
func (fs *hellofs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) {
func (fs *hellofs) Download(ctx context.Context, ref *provider.Reference, openReaderFunc func(md *provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
info, err := fs.lookup(ctx, ref)
if err != nil {
return nil, err
return nil, nil, err
}
if info.Type != provider.ResourceType_RESOURCE_TYPE_FILE {
return nil, errtypes.InternalError("expected a file")
return nil, nil, errtypes.InternalError("expected a file")
}
if info.GetId().GetOpaqueId() != fileid {
return nil, errtypes.InternalError("unknown file")
return nil, nil, errtypes.InternalError("unknown file")
}
if !openReaderFunc(info) {
return info, nil, nil
}
b := &bytes.Buffer{}
b.WriteString(content)
return io.NopCloser(b), nil
return info, io.NopCloser(b), nil
}

View File

@@ -24,6 +24,7 @@ import (
"net/url"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/pkg/errtypes"
"github.com/cs3org/reva/v2/pkg/storage"
)
@@ -153,8 +154,8 @@ func (fs *hellofs) ListRevisions(ctx context.Context, ref *provider.Reference) (
}
// DownloadRevision downloads a revision
func (fs *hellofs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) {
return nil, errtypes.NotSupported("unimplemented")
func (fs *hellofs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string, openReaderFunc func(md *provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
return nil, nil, errtypes.NotSupported("unimplemented")
}
// RestoreRevision restores a revision

View File

@@ -25,18 +25,21 @@ import (
"io"
"net/http"
"net/url"
"path"
"strings"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/cs3org/reva/v2/pkg/appctx"
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/errtypes"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/storage"
"github.com/cs3org/reva/v2/pkg/storage/fs/registry"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)
func init() {
@@ -413,8 +416,17 @@ func (nc *StorageDriver) Upload(ctx context.Context, req storage.UploadRequest,
}
// Download as defined in the storage.FS interface
func (nc *StorageDriver) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) {
return nc.doDownload(ctx, ref.Path)
func (nc *StorageDriver) Download(ctx context.Context, ref *provider.Reference, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
md, err := nc.GetMD(ctx, ref, []string{}, nil)
if err != nil {
return nil, nil, err
}
if !openReaderfunc(md) {
return md, nil, nil
}
reader, err := nc.doDownload(ctx, ref.Path)
return md, reader, err
}
// ListRevisions as defined in the storage.FS interface
@@ -441,12 +453,54 @@ func (nc *StorageDriver) ListRevisions(ctx context.Context, ref *provider.Refere
}
// DownloadRevision as defined in the storage.FS interface
func (nc *StorageDriver) DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error) {
func (nc *StorageDriver) DownloadRevision(ctx context.Context, ref *provider.Reference, key string, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
log := appctx.GetLogger(ctx)
log.Info().Msgf("DownloadRevision %s %s", ref.Path, key)
md, err := nc.GetMD(ctx, ref, []string{}, nil)
if err != nil {
return nil, nil, err
}
revs, err := nc.ListRevisions(ctx, ref)
if err != nil {
return nil, nil, err
}
var ri *provider.ResourceInfo
for _, rev := range revs {
if rev.Key == key {
ri = &provider.ResourceInfo{
// TODO(jfd) we cannot access version content, this will be a problem when trying to fetch version thumbnails
// Opaque
Type: provider.ResourceType_RESOURCE_TYPE_FILE,
Id: &provider.ResourceId{
StorageId: "versions",
OpaqueId: md.Id.OpaqueId + "@" + rev.GetKey(),
},
// Checksum
Etag: rev.Etag,
// MimeType
Mtime: &types.Timestamp{
Seconds: rev.Mtime,
// TODO cs3apis FileVersion should use types.Timestamp instead of uint64
},
Path: path.Join("v", rev.Key),
// PermissionSet
Size: rev.Size,
Owner: md.Owner,
}
}
}
if ri == nil {
return nil, nil, errtypes.NotFound("revision not found")
}
if !openReaderfunc(ri) {
return ri, nil, nil
}
readCloser, err := nc.doDownloadRevision(ctx, ref.Path, key)
return readCloser, err
return ri, readCloser, err
}
// RestoreRevision as defined in the storage.FS interface

View File

@@ -164,6 +164,7 @@ var responses = map[string]Response{
`POST /apps/sciencemesh/~tester/api/storage/GetMD {"ref":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"},"mdKeys":[]}`: {200, `{"opaque":{},"type":1,"id":{"opaque_id":"fileid-/some/path"},"checksum":{},"etag":"deadbeef","mime_type":"text/plain","mtime":{"seconds":1234567890},"path":"/versionedFile","permission_set":{},"size":12345,"canonical_metadata":{},"arbitrary_metadata":{"metadata":{"key1":"val1","key2":"val2","key3":"val3"}}}`, serverStateEmpty},
`GET /apps/sciencemesh/~tester/api/storage/Download/some/file/path.txt `: {200, `the contents of the file`, serverStateEmpty},
`POST /apps/sciencemesh/~tester/api/storage/ListRevisions {"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"/some/path"}`: {200, `[{"opaque":{"map":{"some":{"value":"ZGF0YQ=="}}},"key":"version-12","size":12345,"mtime":1234567890,"etag":"deadb00f"},{"opaque":{"map":{"different":{"value":"c3R1ZmY="}}},"key":"asdf","size":12345,"mtime":1234567890,"etag":"deadbeef"}]`, serverStateEmpty},
`POST /apps/sciencemesh/~tester/api/storage/ListRevisions {"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"}`: {200, `[{"key":"some/revision","size":12345,"mtime":1234567890,"etag":"deadb00f"}]`, serverStateEmpty},
`GET /apps/sciencemesh/~tester/api/storage/DownloadRevision/some%2Frevision/some/file/path.txt `: {200, `the contents of that revision`, serverStateEmpty},
`POST /apps/sciencemesh/~tester/api/storage/RestoreRevision {"ref":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"},"key":"asdf"}`: {200, ``, serverStateEmpty},
`POST /apps/sciencemesh/~tester/api/storage/ListRecycle {"key":"asdf","path":"/some/file.txt"}`: {200, `[{"opaque":{},"key":"some-deleted-version","ref":{"resource_id":{},"path":"/some/file.txt"},"size":12345,"deletion_time":{"seconds":1234567890}}]`, serverStateEmpty},

View File

@@ -39,6 +39,11 @@ import (
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/pkg/xattr"
"github.com/rs/zerolog/log"
"github.com/cs3org/reva/v2/internal/grpc/services/storageprovider"
"github.com/cs3org/reva/v2/pkg/appctx"
"github.com/cs3org/reva/v2/pkg/conversions"
@@ -54,10 +59,6 @@ import (
"github.com/cs3org/reva/v2/pkg/storage/fs/registry"
"github.com/cs3org/reva/v2/pkg/storage/utils/chunking"
"github.com/cs3org/reva/v2/pkg/storage/utils/templates"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/pkg/xattr"
"github.com/rs/zerolog/log"
)
const (
@@ -1544,32 +1545,60 @@ func (fs *owncloudsqlfs) archiveRevision(ctx context.Context, vbp string, ip str
return err
}
func (fs *owncloudsqlfs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) {
func (fs *owncloudsqlfs) Download(ctx context.Context, ref *provider.Reference, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
ip, err := fs.resolve(ctx, ref)
if err != nil {
return nil, errors.Wrap(err, "owncloudsql: error resolving reference")
return nil, nil, errors.Wrap(err, "owncloudsql: error resolving reference")
}
p := fs.toStoragePath(ctx, ip)
// If Download is called for a path shared with the user then the path is
// already wrapped. (fs.resolve wraps the path)
if strings.HasPrefix(p, fs.c.DataDirectory) {
ip = p
}
// check permissions
if perm, err := fs.readPermissions(ctx, ip); err == nil {
if !perm.InitiateFileDownload {
return nil, errtypes.PermissionDenied("")
return nil, nil, errtypes.PermissionDenied("")
}
} else {
if isNotFound(err) {
return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip)))
return nil, nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip)))
}
return nil, errors.Wrap(err, "owncloudsql: error reading permissions")
return nil, nil, errors.Wrap(err, "owncloudsql: error reading permissions")
}
ownerStorageID, err := fs.filecache.GetNumericStorageID(ctx, "home::"+fs.getOwner(ip))
if err != nil {
return nil, nil, err
}
entry, err := fs.filecache.Get(ctx, ownerStorageID, fs.toDatabasePath(ip))
switch {
case err == sql.ErrNoRows:
return nil, nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip)))
case err != nil:
return nil, nil, err
}
md, err := fs.convertToResourceInfo(ctx, entry, ip, nil)
if err != nil {
return nil, nil, err
}
if !openReaderfunc(md) {
return md, nil, nil
}
r, err := os.Open(ip)
if err != nil {
if os.IsNotExist(err) {
return nil, errtypes.NotFound(fs.toStoragePath(ctx, ip))
return nil, nil, errtypes.NotFound(fs.toStoragePath(ctx, ip))
}
return nil, errors.Wrap(err, "owncloudsql: error reading "+ip)
return nil, nil, errors.Wrap(err, "owncloudsql: error reading "+ip)
}
return r, nil
return md, r, nil
}
func (fs *owncloudsqlfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) {
@@ -1623,8 +1652,8 @@ func (fs *owncloudsqlfs) ListRevisions(ctx context.Context, ref *provider.Refere
return revisions, nil
}
func (fs *owncloudsqlfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) {
return nil, errtypes.NotSupported("download revision")
func (fs *owncloudsqlfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
return nil, nil, errtypes.NotSupported("download revision")
}
func (fs *owncloudsqlfs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error {

View File

@@ -616,12 +616,21 @@ func (fs *s3FS) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys,
return finfos, nil
}
func (fs *s3FS) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) {
func (fs *s3FS) Download(ctx context.Context, ref *provider.Reference, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
log := appctx.GetLogger(ctx)
fn, err := fs.resolve(ctx, ref)
if err != nil {
return nil, errors.Wrap(err, "error resolving ref")
return nil, nil, errors.Wrap(err, "error resolving ref")
}
ri, err := fs.GetMD(ctx, ref, nil, []string{"size", "mimetype", "etag"})
if err != nil {
return nil, nil, errors.Wrap(err, "error getting metadata")
}
if !openReaderfunc(ri) {
return ri, nil, nil
}
// use GetObject instead of s3manager.Downloader:
@@ -637,20 +646,20 @@ func (fs *s3FS) Download(ctx context.Context, ref *provider.Reference) (io.ReadC
switch aerr.Code() {
case s3.ErrCodeNoSuchBucket:
case s3.ErrCodeNoSuchKey:
return nil, errtypes.NotFound(fn)
return nil, nil, errtypes.NotFound(fn)
}
}
return nil, errors.Wrap(err, "s3fs: error deleting "+fn)
return nil, nil, errors.Wrap(err, "s3fs: error deleting "+fn)
}
return r.Body, nil
return ri, r.Body, nil
}
func (fs *s3FS) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) {
return nil, errtypes.NotSupported("list revisions")
}
func (fs *s3FS) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) {
return nil, errtypes.NotSupported("download revision")
func (fs *s3FS) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
return nil, nil, errtypes.NotSupported("download revision")
}
func (fs *s3FS) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error {

View File

@@ -23,10 +23,9 @@ import (
"io"
"net/url"
tusd "github.com/tus/tusd/v2/pkg/handler"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
tusd "github.com/tus/tusd/v2/pkg/handler"
)
// FS is the interface to implement access to the storage.
@@ -48,7 +47,7 @@ type FS interface {
// ListFolder returns the resource infos for all children of the referenced resource
ListFolder(ctx context.Context, ref *provider.Reference, mdKeys, fieldMask []string) ([]*provider.ResourceInfo, error)
// Download returns a ReadCloser for the content of the referenced resource
Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error)
Download(ctx context.Context, ref *provider.Reference, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error)
// GetPathByID returns the path for the given resource id relative to the space root
// It should only reveal the path visible to the current user to not leak the names uf unshared parent resources
@@ -80,7 +79,7 @@ type FS interface {
// ListRevisions lists all revisions for the referenced resource
ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error)
// DownloadRevision downloads a revision
DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error)
DownloadRevision(ctx context.Context, ref *provider.Reference, key string, openReaderFunc func(md *provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error)
// RestoreRevision restores a revision
RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error

View File

@@ -33,13 +33,20 @@ import (
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/jellydator/ttlcache/v2"
"github.com/pkg/errors"
tusd "github.com/tus/tusd/v2/pkg/handler"
microstore "go-micro.dev/v4/store"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/errtypes"
"github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/logger"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/v2/pkg/rhttp/datatx/metrics"
"github.com/cs3org/reva/v2/pkg/rhttp/datatx/utils/download"
"github.com/cs3org/reva/v2/pkg/storage"
"github.com/cs3org/reva/v2/pkg/storage/utils/chunking"
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/aspects"
@@ -60,13 +67,6 @@ import (
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/store"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/jellydator/ttlcache/v2"
"github.com/pkg/errors"
tusd "github.com/tus/tusd/v2/pkg/handler"
microstore "go-micro.dev/v4/store"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
)
type CtxKey int
@@ -1054,54 +1054,49 @@ func (fs *Decomposedfs) Delete(ctx context.Context, ref *provider.Reference) (er
}
// Download returns a reader to the specified resource
func (fs *Decomposedfs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) {
func (fs *Decomposedfs) Download(ctx context.Context, ref *provider.Reference, openReaderFunc func(md *provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
ctx, span := tracer.Start(ctx, "Download")
defer span.End()
// check if we are trying to download a revision
// TODO the CS3 api should allow initiating a revision download
if ref.ResourceId != nil && strings.Contains(ref.ResourceId.OpaqueId, node.RevisionIDDelimiter) {
return fs.DownloadRevision(ctx, ref, ref.ResourceId.OpaqueId)
return fs.DownloadRevision(ctx, ref, ref.ResourceId.OpaqueId, openReaderFunc)
}
n, err := fs.lu.NodeFromResource(ctx, ref)
if err != nil {
return nil, errors.Wrap(err, "Decomposedfs: error resolving ref")
return nil, nil, err
}
if !n.Exists {
err = errtypes.NotFound(filepath.Join(n.ParentID, n.Name))
return nil, err
return nil, nil, err
}
rp, err := fs.p.AssemblePermissions(ctx, n)
switch {
case err != nil:
return nil, err
return nil, nil, err
case !rp.InitiateFileDownload:
f, _ := storagespace.FormatReference(ref)
if rp.Stat {
return nil, errtypes.PermissionDenied(f)
return nil, nil, errtypes.PermissionDenied(f)
}
return nil, errtypes.NotFound(f)
return nil, nil, errtypes.NotFound(f)
}
mtime, err := n.GetMTime(ctx)
ri, err := n.AsResourceInfo(ctx, rp, nil, []string{"size", "mimetype", "etag"}, true)
if err != nil {
return nil, errors.Wrap(err, "Decomposedfs: error getting mtime for '"+n.ID+"'")
return nil, nil, err
}
currentEtag, err := node.CalculateEtag(n.ID, mtime)
if err != nil {
return nil, errors.Wrap(err, "Decomposedfs: error calculating etag for '"+n.ID+"'")
var reader io.ReadCloser
if openReaderFunc(ri) {
reader, err = fs.tp.ReadBlob(n)
if err != nil {
return nil, nil, errors.Wrap(err, "Decomposedfs: error download blob '"+n.ID+"'")
}
}
expectedEtag := download.EtagFromContext(ctx)
if currentEtag != expectedEtag {
return nil, errtypes.Aborted(fmt.Sprintf("file changed from etag %s to %s", expectedEtag, currentEtag))
}
reader, err := fs.tp.ReadBlob(n)
if err != nil {
return nil, errors.Wrap(err, "Decomposedfs: error download blob '"+n.ID+"'")
}
return reader, nil
return ri, reader, nil
}
// GetLock returns an existing lock on the given reference

View File

@@ -27,13 +27,15 @@ import (
"time"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/pkg/errors"
"github.com/rogpeppe/go-internal/lockedfile"
"github.com/cs3org/reva/v2/pkg/appctx"
"github.com/cs3org/reva/v2/pkg/errtypes"
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes"
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/pkg/errors"
"github.com/rogpeppe/go-internal/lockedfile"
"github.com/cs3org/reva/v2/pkg/utils"
)
// Revision entries are stored inside the node folder and start with the same uuid as the current version.
@@ -112,14 +114,14 @@ func (fs *Decomposedfs) ListRevisions(ctx context.Context, ref *provider.Referen
// DownloadRevision returns a reader for the specified revision
// FIXME the CS3 api should explicitly allow initiating revision and trash download, a related issue is https://github.com/cs3org/reva/issues/1813
func (fs *Decomposedfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) {
func (fs *Decomposedfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string, openReaderFunc func(md *provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
log := appctx.GetLogger(ctx)
// verify revision key format
kp := strings.SplitN(revisionKey, node.RevisionIDDelimiter, 2)
if len(kp) != 2 {
log.Error().Str("revisionKey", revisionKey).Msg("malformed revisionKey")
return nil, errtypes.NotFound(revisionKey)
return nil, nil, errtypes.NotFound(revisionKey)
}
log.Debug().Str("revisionKey", revisionKey).Msg("DownloadRevision")
@@ -127,39 +129,59 @@ func (fs *Decomposedfs) DownloadRevision(ctx context.Context, ref *provider.Refe
// check if the node is available and has not been deleted
n, err := node.ReadNode(ctx, fs.lu, spaceID, kp[0], false, nil, false)
if err != nil {
return nil, err
return nil, nil, err
}
if !n.Exists {
err = errtypes.NotFound(filepath.Join(n.ParentID, n.Name))
return nil, err
return nil, nil, err
}
rp, err := fs.p.AssemblePermissions(ctx, n)
switch {
case err != nil:
return nil, err
return nil, nil, err
case !rp.ListFileVersions || !rp.InitiateFileDownload: // TODO add explicit permission in the CS3 api?
f, _ := storagespace.FormatReference(ref)
if rp.Stat {
return nil, errtypes.PermissionDenied(f)
return nil, nil, errtypes.PermissionDenied(f)
}
return nil, errtypes.NotFound(f)
return nil, nil, errtypes.NotFound(f)
}
contentPath := fs.lu.InternalPath(spaceID, revisionKey)
blobid, blobsize, err := fs.lu.ReadBlobIDAndSizeAttr(ctx, contentPath, nil)
if err != nil {
return nil, errors.Wrapf(err, "Decomposedfs: could not read blob id and size for revision '%s' of node '%s'", n.ID, revisionKey)
return nil, nil, errors.Wrapf(err, "Decomposedfs: could not read blob id and size for revision '%s' of node '%s'", kp[1], n.ID)
}
revisionNode := node.Node{SpaceID: spaceID, BlobID: blobid, Blobsize: blobsize} // blobsize is needed for the s3ng blobstore
reader, err := fs.tp.ReadBlob(&revisionNode)
ri, err := n.AsResourceInfo(ctx, rp, nil, []string{"size", "mimetype", "etag"}, true)
if err != nil {
return nil, errors.Wrapf(err, "Decomposedfs: could not download blob of revision '%s' for node '%s'", n.ID, revisionKey)
return nil, nil, err
}
return reader, nil
// update resource info with revision data
mtime, err := time.Parse(time.RFC3339Nano, kp[1])
if err != nil {
return nil, nil, errors.Wrapf(err, "Decomposedfs: could not parse mtime for revision '%s' of node '%s'", kp[1], n.ID)
}
ri.Size = uint64(blobsize)
ri.Mtime = utils.TimeToTS(mtime)
ri.Etag, err = node.CalculateEtag(n.ID, mtime)
if err != nil {
return nil, nil, errors.Wrapf(err, "error calculating etag for revision '%s' of node '%s'", kp[1], n.ID)
}
var reader io.ReadCloser
if openReaderFunc(ri) {
reader, err = fs.tp.ReadBlob(&revisionNode)
if err != nil {
return nil, nil, errors.Wrapf(err, "Decomposedfs: could not download blob of revision '%s' for node '%s'", n.ID, revisionKey)
}
}
return ri, reader, nil
}
// RestoreRevision restores the specified revision of the resource

View File

@@ -228,6 +228,10 @@ func (fs *Decomposedfs) InitiateUpload(ctx context.Context, ref *provider.Refere
}
}
if session.MTime().IsZero() {
session.SetMetadata("mtime", utils.TimeToOCMtime(time.Now()))
}
log.Debug().Str("uploadid", session.ID()).Str("spaceid", n.SpaceID).Str("nodeid", n.ID).Interface("metadata", metadata).Msg("Decomposedfs: resolved filename")
_, err = node.CheckQuota(ctx, n.SpaceRoot, n.Exists, uint64(n.Blobsize), uint64(session.Size()))

View File

@@ -1834,13 +1834,23 @@ func (fs *eosfs) moveShadow(ctx context.Context, oldPath, newPath string) error
return fs.c.Rename(ctx, auth, oldfn, newfn)
}
func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) {
func (fs *eosfs) Download(ctx context.Context, ref *provider.Reference, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
fn, auth, err := fs.resolveRefForbidShareFolder(ctx, ref)
if err != nil {
return nil, err
return nil, nil, err
}
return fs.c.Read(ctx, auth, fn)
md, err := fs.GetMD(ctx, ref, nil, nil)
if err != nil {
return nil, nil, err
}
if !openReaderfunc(md) {
return md, nil, nil
}
reader, err := fs.c.Read(ctx, auth, fn)
return md, reader, err
}
func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) {
@@ -1886,37 +1896,43 @@ func (fs *eosfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]
return revisions, nil
}
func (fs *eosfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) {
func (fs *eosfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
var auth eosclient.Authorization
var fn string
var err error
md, err := fs.GetMD(ctx, ref, nil, nil)
if err != nil {
return nil, nil, err
}
if !fs.conf.EnableHome && fs.conf.ImpersonateOwnerforRevisions {
// We need to access the revisions for a non-home reference.
// We'll get the owner of the particular resource and impersonate them
// if we have access to it.
md, err := fs.GetMD(ctx, ref, nil, nil)
if err != nil {
return nil, err
}
fn = fs.wrap(ctx, md.Path)
fn = fs.wrap(ctx, md.Path)
if md.PermissionSet.InitiateFileDownload {
auth, err = fs.getUIDGateway(ctx, md.Owner)
if err != nil {
return nil, err
return nil, nil, err
}
} else {
return nil, errtypes.PermissionDenied("eosfs: user doesn't have permissions to download revisions")
return nil, nil, errtypes.PermissionDenied("eosfs: user doesn't have permissions to download revisions")
}
} else {
fn, auth, err = fs.resolveRefForbidShareFolder(ctx, ref)
if err != nil {
return nil, err
return nil, nil, err
}
}
return fs.c.ReadVersion(ctx, auth, fn, revisionKey)
if !openReaderfunc(md) {
return md, nil, nil
}
reader, err := fs.c.ReadVersion(ctx, auth, fn, revisionKey)
return md, reader, err
}
func (fs *eosfs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error {

View File

@@ -1045,25 +1045,42 @@ func (fs *localfs) listShareFolderRoot(ctx context.Context, home string, mdKeys
return finfos, nil
}
func (fs *localfs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) {
func (fs *localfs) Download(ctx context.Context, ref *provider.Reference, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
fn, err := fs.resolve(ctx, ref)
if err != nil {
return nil, errors.Wrap(err, "localfs: error resolving ref")
return nil, nil, errors.Wrap(err, "localfs: error resolving ref")
}
if fs.isShareFolder(ctx, fn) {
return nil, errtypes.PermissionDenied("localfs: cannot download under the virtual share folder")
return nil, nil, errtypes.PermissionDenied("localfs: cannot download under the virtual share folder")
}
fn = fs.wrap(ctx, fn)
md, err := os.Stat(fn)
if err != nil {
if os.IsNotExist(err) {
return nil, nil, errtypes.NotFound(fn)
}
return nil, nil, errors.Wrap(err, "localfs: error stating "+fn)
}
ri, err := fs.normalize(ctx, md, fn, []string{"size", "mimetype", "etag"})
if err != nil {
return nil, nil, err
}
if !openReaderfunc(ri) {
return ri, nil, nil
}
r, err := os.Open(fn)
if err != nil {
if os.IsNotExist(err) {
return nil, errtypes.NotFound(fn)
return nil, nil, errtypes.NotFound(fn)
}
return nil, errors.Wrap(err, "localfs: error reading "+fn)
return nil, nil, errors.Wrap(err, "localfs: error reading "+fn)
}
return r, nil
return ri, r, nil
}
func (fs *localfs) archiveRevision(ctx context.Context, np string) error {
@@ -1117,28 +1134,42 @@ func (fs *localfs) ListRevisions(ctx context.Context, ref *provider.Reference) (
return revisions, nil
}
func (fs *localfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) {
func (fs *localfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
np, err := fs.resolve(ctx, ref)
if err != nil {
return nil, errors.Wrap(err, "localfs: error resolving ref")
return nil, nil, errors.Wrap(err, "localfs: error resolving ref")
}
if fs.isShareFolder(ctx, np) {
return nil, errtypes.PermissionDenied("localfs: cannot download revisions under the virtual share folder")
return nil, nil, errtypes.PermissionDenied("localfs: cannot download revisions under the virtual share folder")
}
versionsDir := fs.wrapVersions(ctx, np)
vp := path.Join(versionsDir, revisionKey)
r, err := os.Open(vp)
md, err := os.Stat(vp)
if err != nil {
if os.IsNotExist(err) {
return nil, errtypes.NotFound(vp)
return nil, nil, errtypes.NotFound(vp)
}
return nil, errors.Wrap(err, "localfs: error reading "+vp)
return nil, nil, errors.Wrap(err, "localfs: error stating "+vp)
}
return r, nil
ri, err := fs.normalize(ctx, md, vp, []string{"size", "mimetype", "etag"})
if err != nil {
return nil, nil, err
}
if !openReaderfunc(ri) {
return ri, nil, nil
}
r, err := os.Open(vp)
if err != nil {
return nil, nil, errors.Wrap(err, "localfs: error reading "+vp)
}
return ri, r, nil
}
func (fs *localfs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error {

View File

@@ -34,14 +34,15 @@ import (
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc/metadata"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/errtypes"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/v2/pkg/utils"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc/metadata"
)
var tracer trace.Tracer
@@ -212,7 +213,7 @@ func (cs3 *CS3) Upload(ctx context.Context, req UploadRequest) (*UploadResponse,
}
if req.MTime != (time.Time{}) {
// The format of the X-OC-Mtime header is <epoch>.<nanoseconds>, e.g. '1691053416.934129485'
ifuReq.Opaque = utils.AppendPlainToOpaque(ifuReq.Opaque, "X-OC-Mtime", strconv.Itoa(int(req.MTime.Unix()))+"."+strconv.Itoa(req.MTime.Nanosecond()))
ifuReq.Opaque = utils.AppendPlainToOpaque(ifuReq.Opaque, "X-OC-Mtime", utils.TimeToOCMtime(req.MTime))
}
ifuReq.Opaque = utils.AppendPlainToOpaque(ifuReq.Opaque, net.HeaderUploadLength, strconv.FormatInt(int64(len(req.Content)), 10))

View File

@@ -23,9 +23,9 @@ import (
"io"
"net/url"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
tusd "github.com/tus/tusd/v2/pkg/handler"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/pkg/storage"
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/upload"
"github.com/cs3org/reva/v2/pkg/storagespace"
@@ -361,7 +361,7 @@ func (f *FS) Upload(ctx context.Context, req storage.UploadRequest, uploadFunc s
return res0, res1
}
func (f *FS) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) {
func (f *FS) Download(ctx context.Context, ref *provider.Reference, openReaderFunc func(md *provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
var (
err error
unhook UnHook
@@ -370,22 +370,22 @@ func (f *FS) Download(ctx context.Context, ref *provider.Reference) (io.ReadClos
for _, hook := range f.hooks {
ctx, unhook, err = hook("Download", ctx, ref.GetResourceId().GetSpaceId())
if err != nil {
return nil, err
return nil, nil, err
}
if unhook != nil {
unhooks = append(unhooks, unhook)
}
}
res0, res1 := f.next.Download(ctx, ref)
res0, res1, res2 := f.next.Download(ctx, ref, openReaderFunc)
for _, unhook := range unhooks {
if err := unhook(); err != nil {
return nil, err
return nil, nil, err
}
}
return res0, res1
return res0, res1, res2
}
func (f *FS) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) {
@@ -415,7 +415,7 @@ func (f *FS) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*pro
return res0, res1
}
func (f *FS) DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error) {
func (f *FS) DownloadRevision(ctx context.Context, ref *provider.Reference, key string, openReaderFunc func(md *provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error) {
var (
err error
unhook UnHook
@@ -424,22 +424,22 @@ func (f *FS) DownloadRevision(ctx context.Context, ref *provider.Reference, key
for _, hook := range f.hooks {
ctx, unhook, err = hook("DownloadRevision", ctx, ref.GetResourceId().GetSpaceId())
if err != nil {
return nil, err
return nil, nil, err
}
if unhook != nil {
unhooks = append(unhooks, unhook)
}
}
res0, res1 := f.next.DownloadRevision(ctx, ref, key)
res0, res1, res2 := f.next.DownloadRevision(ctx, ref, key, openReaderFunc)
for _, unhook := range unhooks {
if err := unhook(); err != nil {
return nil, err
return nil, nil, err
}
}
return res0, res1
return res0, res1, res2
}
func (f *FS) RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error {

View File

@@ -201,6 +201,11 @@ func MTimeToTime(v string) (t time.Time, err error) {
return time.Unix(sec, nsec), err
}
// TimeToOCMtime converts a Go time.Time to a string in the form "<unix>.<nanoseconds>"
func TimeToOCMtime(t time.Time) string {
return strconv.FormatInt(t.Unix(), 10) + "." + strconv.FormatInt(int64(t.Nanosecond()), 10)
}
// ExtractGranteeID returns the ID, user or group, set in the GranteeId object
func ExtractGranteeID(grantee *provider.Grantee) (*userpb.UserId, *grouppb.GroupId) {
switch t := grantee.Id.(type) {

2
vendor/modules.txt vendored
View File

@@ -367,7 +367,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1
github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1
github.com/cs3org/go-cs3apis/cs3/tx/v1beta1
github.com/cs3org/go-cs3apis/cs3/types/v1beta1
# github.com/cs3org/reva/v2 v2.26.5-0.20241111162950-e77dd61e7edb
# github.com/cs3org/reva/v2 v2.26.5
## explicit; go 1.22.0
github.com/cs3org/reva/v2/cmd/revad/internal/grace
github.com/cs3org/reva/v2/cmd/revad/runtime