mirror of https://github.com/cloudreve/Cloudreve
fix(dbfs): enforce root protection for single file share
parent
d60e400f83
commit
7b5e0e8581
|
@ -448,6 +448,10 @@ func (f *DBFS) Get(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.Fil
|
||||||
return nil, fmt.Errorf("failed to get target file: %w", err)
|
return nil, fmt.Errorf("failed to get target file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.notRoot && target.IsRootFolder() {
|
||||||
|
return nil, fs.ErrNotSupportedAction.WithError(fmt.Errorf("cannot operate root file"))
|
||||||
|
}
|
||||||
|
|
||||||
if o.extendedInfo && target != nil {
|
if o.extendedInfo && target != nil {
|
||||||
extendedInfo := &fs.FileExtendedInfo{
|
extendedInfo := &fs.FileExtendedInfo{
|
||||||
StorageUsed: target.SizeUsed(),
|
StorageUsed: target.SizeUsed(),
|
||||||
|
|
|
@ -25,6 +25,7 @@ type dbfsOption struct {
|
||||||
noChainedCreation bool
|
noChainedCreation bool
|
||||||
streamListResponseCallback func(parent fs.File, file []fs.File)
|
streamListResponseCallback func(parent fs.File, file []fs.File)
|
||||||
ancestor *File
|
ancestor *File
|
||||||
|
notRoot bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDbfsOption() *dbfsOption {
|
func newDbfsOption() *dbfsOption {
|
||||||
|
@ -56,6 +57,13 @@ func WithFilePublicMetadata() fs.Option {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithNotRoot force the get result cannot be a root folder
|
||||||
|
func WithNotRoot() fs.Option {
|
||||||
|
return optionFunc(func(o *dbfsOption) {
|
||||||
|
o.notRoot = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// WithContextHint enables generating context hint for the list operation.
|
// WithContextHint enables generating context hint for the list operation.
|
||||||
func WithContextHint() fs.Option {
|
func WithContextHint() fs.Option {
|
||||||
return optionFunc(func(o *dbfsOption) {
|
return optionFunc(func(o *dbfsOption) {
|
||||||
|
|
|
@ -183,7 +183,7 @@ func (n *shareNavigator) To(ctx context.Context, path *fs.URI) (*File, error) {
|
||||||
elements := path.Elements()
|
elements := path.Elements()
|
||||||
|
|
||||||
// If target is root of single file share, the root itself is the target.
|
// If target is root of single file share, the root itself is the target.
|
||||||
if len(elements) <= 1 && n.singleFileShare {
|
if len(elements) == 1 && n.singleFileShare {
|
||||||
file, err := n.latestSharedSingleFile(ctx)
|
file, err := n.latestSharedSingleFile(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -176,7 +176,7 @@ func (f *DBFS) PrepareUpload(ctx context.Context, req *fs.UploadRequest, opts ..
|
||||||
|
|
||||||
func (f *DBFS) CompleteUpload(ctx context.Context, session *fs.UploadSession) (fs.File, error) {
|
func (f *DBFS) CompleteUpload(ctx context.Context, session *fs.UploadSession) (fs.File, error) {
|
||||||
// Get placeholder file
|
// Get placeholder file
|
||||||
file, err := f.Get(ctx, session.Props.Uri, WithFileEntities())
|
file, err := f.Get(ctx, session.Props.Uri, WithFileEntities(), WithNotRoot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get placeholder file: %w", err)
|
return nil, fmt.Errorf("failed to get placeholder file: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -270,7 +270,7 @@ func (f *DBFS) CompleteUpload(ctx context.Context, session *fs.UploadSession) (f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err = f.Get(ctx, session.Props.Uri, WithFileEntities())
|
file, err = f.Get(ctx, session.Props.Uri, WithFileEntities(), WithNotRoot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get updated file: %w", err)
|
return nil, fmt.Errorf("failed to get updated file: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -284,7 +284,7 @@ func (f *DBFS) CompleteUpload(ctx context.Context, session *fs.UploadSession) (f
|
||||||
// - File unlocked, upload session not valid
|
// - File unlocked, upload session not valid
|
||||||
func (f *DBFS) CancelUploadSession(ctx context.Context, path *fs.URI, sessionID string, session *fs.UploadSession) ([]fs.Entity, error) {
|
func (f *DBFS) CancelUploadSession(ctx context.Context, path *fs.URI, sessionID string, session *fs.UploadSession) ([]fs.Entity, error) {
|
||||||
// Get placeholder file
|
// Get placeholder file
|
||||||
file, err := f.Get(ctx, path, WithFileEntities())
|
file, err := f.Get(ctx, path, WithFileEntities(), WithNotRoot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get placeholder file: %w", err)
|
return nil, fmt.Errorf("failed to get placeholder file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,6 +158,7 @@ type (
|
||||||
ExtendedInfo() *FileExtendedInfo
|
ExtendedInfo() *FileExtendedInfo
|
||||||
FolderSummary() *FolderSummary
|
FolderSummary() *FolderSummary
|
||||||
Capabilities() *boolset.BooleanSet
|
Capabilities() *boolset.BooleanSet
|
||||||
|
IsRootFolder() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
Entities []Entity
|
Entities []Entity
|
||||||
|
|
|
@ -27,7 +27,7 @@ func (m *manager) CreateArchive(ctx context.Context, uris []*fs.URI, writer io.W
|
||||||
// List all top level files
|
// List all top level files
|
||||||
files := make([]fs.File, 0, len(uris))
|
files := make([]fs.File, 0, len(uris))
|
||||||
for _, uri := range uris {
|
for _, uri := range uris {
|
||||||
file, err := m.Get(ctx, uri, dbfs.WithFileEntities(), dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityDownloadFile))
|
file, err := m.Get(ctx, uri, dbfs.WithFileEntities(), dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityDownloadFile), dbfs.WithNotRoot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("failed to get file %s: %w", uri, err)
|
return 0, fmt.Errorf("failed to get file %s: %w", uri, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -227,7 +227,7 @@ func (l *manager) Restore(ctx context.Context, path ...*fs.URI) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *manager) CreateOrUpdateShare(ctx context.Context, path *fs.URI, args *CreateShareArgs) (*ent.Share, error) {
|
func (l *manager) CreateOrUpdateShare(ctx context.Context, path *fs.URI, args *CreateShareArgs) (*ent.Share, error) {
|
||||||
file, err := l.fs.Get(ctx, path, dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityShare))
|
file, err := l.fs.Get(ctx, path, dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityShare), dbfs.WithNotRoot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, serializer.NewError(serializer.CodeNotFound, "src file not found", err)
|
return nil, serializer.NewError(serializer.CodeNotFound, "src file not found", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *manager) CreateViewerSession(ctx context.Context, uri *fs.URI, version string, viewer *setting.Viewer) (*ViewerSession, error) {
|
func (m *manager) CreateViewerSession(ctx context.Context, uri *fs.URI, version string, viewer *setting.Viewer) (*ViewerSession, error) {
|
||||||
file, err := m.fs.Get(ctx, uri, dbfs.WithFileEntities())
|
file, err := m.fs.Get(ctx, uri, dbfs.WithFileEntities(), dbfs.WithNotRoot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,7 +171,7 @@ func (m *ExtractArchiveTask) createSlaveExtractTask(ctx context.Context, dep dep
|
||||||
fm := manager.NewFileManager(dep, user)
|
fm := manager.NewFileManager(dep, user)
|
||||||
|
|
||||||
// Get entity source to extract
|
// Get entity source to extract
|
||||||
archiveFile, err := fm.Get(ctx, uri, dbfs.WithFileEntities(), dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityDownloadFile))
|
archiveFile, err := fm.Get(ctx, uri, dbfs.WithFileEntities(), dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityDownloadFile), dbfs.WithNotRoot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return task.StatusError, fmt.Errorf("failed to get archive file: %s (%w)", err, queue.CriticalErr)
|
return task.StatusError, fmt.Errorf("failed to get archive file: %s (%w)", err, queue.CriticalErr)
|
||||||
}
|
}
|
||||||
|
@ -256,7 +256,7 @@ func (m *ExtractArchiveTask) masterExtractArchive(ctx context.Context, dep depen
|
||||||
fm := manager.NewFileManager(dep, user)
|
fm := manager.NewFileManager(dep, user)
|
||||||
|
|
||||||
// Get entity source to extract
|
// Get entity source to extract
|
||||||
archiveFile, err := fm.Get(ctx, uri, dbfs.WithFileEntities(), dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityDownloadFile))
|
archiveFile, err := fm.Get(ctx, uri, dbfs.WithFileEntities(), dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityDownloadFile), dbfs.WithNotRoot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return task.StatusError, fmt.Errorf("failed to get archive file: %s (%w)", err, queue.CriticalErr)
|
return task.StatusError, fmt.Errorf("failed to get archive file: %s (%w)", err, queue.CriticalErr)
|
||||||
}
|
}
|
||||||
|
@ -413,7 +413,7 @@ func (m *ExtractArchiveTask) masterDownloadZip(ctx context.Context, dep dependen
|
||||||
fm := manager.NewFileManager(dep, user)
|
fm := manager.NewFileManager(dep, user)
|
||||||
|
|
||||||
// Get entity source to extract
|
// Get entity source to extract
|
||||||
archiveFile, err := fm.Get(ctx, uri, dbfs.WithFileEntities(), dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityDownloadFile))
|
archiveFile, err := fm.Get(ctx, uri, dbfs.WithFileEntities(), dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityDownloadFile), dbfs.WithNotRoot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return task.StatusError, fmt.Errorf("failed to get archive file: %s (%w)", err, queue.CriticalErr)
|
return task.StatusError, fmt.Errorf("failed to get archive file: %s (%w)", err, queue.CriticalErr)
|
||||||
}
|
}
|
||||||
|
|
|
@ -621,7 +621,7 @@ func (s *GetFileInfoService) Get(c *gin.Context) (*FileResponse, error) {
|
||||||
return nil, serializer.NewError(serializer.CodeParamErr, "unknown uri", err)
|
return nil, serializer.NewError(serializer.CodeParamErr, "unknown uri", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []fs.Option{dbfs.WithFilePublicMetadata()}
|
opts := []fs.Option{dbfs.WithFilePublicMetadata(), dbfs.WithNotRoot()}
|
||||||
if s.ExtendedInfo {
|
if s.ExtendedInfo {
|
||||||
opts = append(opts, dbfs.WithExtendedInfo(), dbfs.WithEntityUser(), dbfs.WithFileShareIfOwned())
|
opts = append(opts, dbfs.WithExtendedInfo(), dbfs.WithEntityUser(), dbfs.WithFileShareIfOwned())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@ package explorer
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/cloudreve/Cloudreve/v4/application/dependency"
|
"github.com/cloudreve/Cloudreve/v4/application/dependency"
|
||||||
"github.com/cloudreve/Cloudreve/v4/ent"
|
"github.com/cloudreve/Cloudreve/v4/ent"
|
||||||
"github.com/cloudreve/Cloudreve/v4/inventory"
|
"github.com/cloudreve/Cloudreve/v4/inventory"
|
||||||
|
@ -18,8 +21,6 @@ import (
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/wopi"
|
"github.com/cloudreve/Cloudreve/v4/pkg/wopi"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type WopiService struct {
|
type WopiService struct {
|
||||||
|
@ -68,7 +69,7 @@ func (service *WopiService) RefreshLock(c *gin.Context) error {
|
||||||
l := dep.Logger()
|
l := dep.Logger()
|
||||||
|
|
||||||
// Make sure file exists and readable
|
// Make sure file exists and readable
|
||||||
file, err := m.Get(c, uri, dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityLockFile))
|
file, err := m.Get(c, uri, dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityLockFile), dbfs.WithNotRoot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get file: %w", err)
|
return fmt.Errorf("failed to get file: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -105,7 +106,7 @@ func (service *WopiService) Lock(c *gin.Context) error {
|
||||||
l := dep.Logger()
|
l := dep.Logger()
|
||||||
|
|
||||||
// Make sure file exists and readable
|
// Make sure file exists and readable
|
||||||
file, err := m.Get(c, uri, dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityLockFile))
|
file, err := m.Get(c, uri, dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityLockFile), dbfs.WithNotRoot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get file: %w", err)
|
return fmt.Errorf("failed to get file: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -159,7 +160,7 @@ func (service *WopiService) PutContent(c *gin.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure file exists and readable
|
// Make sure file exists and readable
|
||||||
file, err := m.Get(c, uri, dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityUploadFile))
|
file, err := m.Get(c, uri, dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityUploadFile), dbfs.WithNotRoot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get file: %w", err)
|
return fmt.Errorf("failed to get file: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -244,7 +245,7 @@ func (service *WopiService) GetFile(c *gin.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure file exists and readable
|
// Make sure file exists and readable
|
||||||
file, err := m.Get(c, uri, dbfs.WithExtendedInfo(), dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityDownloadFile))
|
file, err := m.Get(c, uri, dbfs.WithExtendedInfo(), dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityDownloadFile), dbfs.WithNotRoot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get file: %w", err)
|
return fmt.Errorf("failed to get file: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -285,6 +286,7 @@ func (service *WopiService) FileInfo(c *gin.Context) (*WopiFileInfo, error) {
|
||||||
opts := []fs.Option{
|
opts := []fs.Option{
|
||||||
dbfs.WithFilePublicMetadata(),
|
dbfs.WithFilePublicMetadata(),
|
||||||
dbfs.WithExtendedInfo(),
|
dbfs.WithExtendedInfo(),
|
||||||
|
dbfs.WithNotRoot(),
|
||||||
dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityDownloadFile, dbfs.NavigatorCapabilityInfo, dbfs.NavigatorCapabilityUploadFile),
|
dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityDownloadFile, dbfs.NavigatorCapabilityInfo, dbfs.NavigatorCapabilityUploadFile),
|
||||||
}
|
}
|
||||||
file, err := m.Get(c, uri, opts...)
|
file, err := m.Get(c, uri, opts...)
|
||||||
|
|
Loading…
Reference in New Issue