package dbfs import ( "context" "fmt" "github.com/cloudreve/Cloudreve/v4/application/constants" "github.com/cloudreve/Cloudreve/v4/ent" "github.com/cloudreve/Cloudreve/v4/inventory" "github.com/cloudreve/Cloudreve/v4/inventory/types" "github.com/cloudreve/Cloudreve/v4/pkg/boolset" "github.com/cloudreve/Cloudreve/v4/pkg/cache" "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs" "github.com/cloudreve/Cloudreve/v4/pkg/hashid" "github.com/cloudreve/Cloudreve/v4/pkg/logging" "github.com/cloudreve/Cloudreve/v4/pkg/serializer" "github.com/cloudreve/Cloudreve/v4/pkg/setting" ) var ( ErrShareNotFound = serializer.NewError(serializer.CodeNotFound, "Shared file does not exist", nil) ErrNotPurchased = serializer.NewError(serializer.CodePurchaseRequired, "You need to purchased this share", nil) ) const ( PurchaseTicketHeader = constants.CrHeaderPrefix + "Purchase-Ticket" ) var shareNavigatorCapability = &boolset.BooleanSet{} // NewShareNavigator creates a navigator for user's "shared" file system. func NewShareNavigator(u *ent.User, fileClient inventory.FileClient, shareClient inventory.ShareClient, l logging.Logger, config *setting.DBFS, hasher hashid.Encoder) Navigator { n := &shareNavigator{ user: u, l: l, fileClient: fileClient, shareClient: shareClient, config: config, } n.baseNavigator = newBaseNavigator(fileClient, defaultFilter, u, hasher, config) return n } type ( shareNavigator struct { l logging.Logger user *ent.User fileClient inventory.FileClient shareClient inventory.ShareClient config *setting.DBFS *baseNavigator shareRoot *File singleFileShare bool ownerRoot *File share *ent.Share owner *ent.User disableRecycle bool persist func() } shareNavigatorState struct { ShareRoot *File OwnerRoot *File SingleFileShare bool Share *ent.Share Owner *ent.User } ) func (n *shareNavigator) PersistState(kv cache.Driver, key string) { n.disableRecycle = true n.persist = func() { kv.Set(key, shareNavigatorState{ ShareRoot: n.shareRoot, OwnerRoot: n.ownerRoot, SingleFileShare: n.singleFileShare, Share: n.share, Owner: n.owner, }, ContextHintTTL) } } func (n *shareNavigator) RestoreState(s State) error { n.disableRecycle = true if state, ok := s.(shareNavigatorState); ok { n.shareRoot = state.ShareRoot n.ownerRoot = state.OwnerRoot n.singleFileShare = state.SingleFileShare n.share = state.Share n.owner = state.Owner return nil } return fmt.Errorf("invalid state type: %T", s) } func (n *shareNavigator) Recycle() { if n.persist != nil { n.persist() n.persist = nil } if !n.disableRecycle { if n.ownerRoot != nil { n.ownerRoot.Recycle() } else if n.shareRoot != nil { n.shareRoot.Recycle() } } } func (n *shareNavigator) Root(ctx context.Context, path *fs.URI) (*File, error) { ctx = context.WithValue(ctx, inventory.LoadShareUser{}, true) ctx = context.WithValue(ctx, inventory.LoadUserGroup{}, true) ctx = context.WithValue(ctx, inventory.LoadShareFile{}, true) share, err := n.shareClient.GetByHashID(ctx, path.ID(hashid.EncodeUserID(n.hasher, n.user.ID))) if err != nil { return nil, ErrShareNotFound.WithError(err) } if err := inventory.IsValidShare(share); err != nil { return nil, ErrShareNotFound.WithError(err) } n.owner = share.Edges.User // Check password if share.Password != "" && share.Password != path.Password() { return nil, ErrShareIncorrectPassword } // Share permission setting should overwrite root folder's permission n.shareRoot = newFile(nil, share.Edges.File) // Find the user side root of the file. ownerRoot, err := n.findRoot(ctx, n.shareRoot) if err != nil { return nil, err } if n.shareRoot.Type() == types.FileTypeFile { n.singleFileShare = true n.shareRoot = n.shareRoot.Parent } n.shareRoot.Path[pathIndexUser] = path.Root() n.shareRoot.OwnerModel = n.owner n.shareRoot.IsUserRoot = true n.shareRoot.disableView = (share.Props == nil || !share.Props.ShareView) && n.user.ID != n.owner.ID n.shareRoot.CapabilitiesBs = n.Capabilities(false).Capability // Check if any ancestors is deleted if ownerRoot.Name() != inventory.RootFolderName { return nil, ErrShareNotFound } if n.user.ID != n.owner.ID && !n.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionShareDownload)) { return nil, serializer.NewError( serializer.CodeNoPermissionErr, fmt.Sprintf("You don't have permission to access share links"), err, ) } n.ownerRoot = ownerRoot n.ownerRoot.Path[pathIndexRoot] = newMyIDUri(hashid.EncodeUserID(n.hasher, n.owner.ID)) n.share = share return n.shareRoot, nil } func (n *shareNavigator) To(ctx context.Context, path *fs.URI) (*File, error) { if n.shareRoot == nil { root, err := n.Root(ctx, path) if err != nil { return nil, err } n.shareRoot = root } current, lastAncestor := n.shareRoot, n.shareRoot elements := path.Elements() // If target is root of single file share, the root itself is the target. if len(elements) == 1 && n.singleFileShare { file, err := n.latestSharedSingleFile(ctx) if err != nil { return nil, err } if len(elements) == 1 && file.Name() != elements[0] { return nil, fs.ErrPathNotExist } return file, nil } var err error for index, element := range elements { lastAncestor = current current, err = n.walkNext(ctx, current, element, index == len(elements)-1) if err != nil { return lastAncestor, fmt.Errorf("failed to walk into %q: %w", element, err) } } return current, nil } func (n *shareNavigator) walkNext(ctx context.Context, root *File, next string, isLeaf bool) (*File, error) { nextFile, err := n.baseNavigator.walkNext(ctx, root, next, isLeaf) if err != nil { return nil, err } return nextFile, nil } func (n *shareNavigator) Children(ctx context.Context, parent *File, args *ListArgs) (*ListResult, error) { if n.singleFileShare { file, err := n.latestSharedSingleFile(ctx) if err != nil { return nil, err } return &ListResult{ Files: []*File{file}, Pagination: &inventory.PaginationResults{}, SingleFileView: true, }, nil } return n.baseNavigator.children(ctx, parent, args) } func (n *shareNavigator) latestSharedSingleFile(ctx context.Context) (*File, error) { if n.singleFileShare { file, err := n.fileClient.GetByID(ctx, n.share.Edges.File.ID) if err != nil { return nil, err } f := newFile(n.shareRoot, file) f.OwnerModel = n.shareRoot.OwnerModel return f, nil } return nil, fs.ErrPathNotExist } func (n *shareNavigator) Capabilities(isSearching bool) *fs.NavigatorProps { res := &fs.NavigatorProps{ Capability: shareNavigatorCapability, OrderDirectionOptions: fullOrderDirectionOption, OrderByOptions: fullOrderByOption, MaxPageSize: n.config.MaxPageSize, } if isSearching { res.OrderByOptions = nil res.OrderDirectionOptions = nil } return res } func (n *shareNavigator) FollowTx(ctx context.Context) (func(), error) { if _, ok := ctx.Value(inventory.TxCtx{}).(*inventory.Tx); !ok { return nil, fmt.Errorf("navigator: no inherited transaction found in context") } newFileClient, _, _, err := inventory.WithTx(ctx, n.fileClient) if err != nil { return nil, err } newSharClient, _, _, err := inventory.WithTx(ctx, n.shareClient) oldFileClient, oldShareClient := n.fileClient, n.shareClient revert := func() { n.fileClient = oldFileClient n.shareClient = oldShareClient n.baseNavigator.fileClient = oldFileClient } n.fileClient = newFileClient n.shareClient = newSharClient n.baseNavigator.fileClient = newFileClient return revert, nil } func (n *shareNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error { switch hookType { case fs.HookTypeBeforeDownload: if n.singleFileShare { return n.shareClient.Downloaded(ctx, n.share) } } return nil } func (n *shareNavigator) Walk(ctx context.Context, levelFiles []*File, limit, depth int, f WalkFunc) error { return n.baseNavigator.walk(ctx, levelFiles, limit, depth, f) } func (n *shareNavigator) GetView(ctx context.Context, file *File) *types.ExplorerView { return file.View() }