feat(og): reuse existing share service and show thumbnail if possible

pull/3234/head
Aaron Liu 8 months ago
parent 572859fd1f
commit d06a29e8a3

@ -2,26 +2,27 @@ package middleware
import ( import (
"bytes" "bytes"
"crypto/subtle" "errors"
"fmt" "fmt"
"html/template" "html/template"
"net/url" "net/url"
"strings" "strings"
"github.com/cloudreve/Cloudreve/v4/application/constants"
"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/inventory"
"github.com/cloudreve/Cloudreve/v4/inventory/types" "github.com/cloudreve/Cloudreve/v4/inventory/types"
"github.com/cloudreve/Cloudreve/v4/pkg/cluster/routes" "github.com/cloudreve/Cloudreve/v4/pkg/cluster/routes"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs"
"github.com/cloudreve/Cloudreve/v4/pkg/hashid" "github.com/cloudreve/Cloudreve/v4/pkg/hashid"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/cloudreve/Cloudreve/v4/pkg/util"
"github.com/cloudreve/Cloudreve/v4/service/explorer"
"github.com/cloudreve/Cloudreve/v4/service/share"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
const ( const (
ogStatusInvalidLink = "Invalid Link" ogStatusInvalidLink = "Invalid Link"
ogStatusShareUnavailable = "Share Unavailable"
ogStatusPasswordRequired = "Password Required"
ogStatusNeedLogin = "Login Required"
) )
type ogData struct { type ogData struct {
@ -114,22 +115,14 @@ func extractShareParams(c *gin.Context) (id, password string) {
} }
} else if urlPath == "/home" || urlPath == "/home/" { } else if urlPath == "/home" || urlPath == "/home/" {
rawPath := c.Query("path") rawPath := c.Query("path")
if strings.HasPrefix(rawPath, "cloudreve://") { uri, err := fs.NewUriFromString(rawPath)
u, err := url.Parse(rawPath) if err != nil || uri.FileSystem() != constants.FileSystemShare {
if err != nil {
return "", ""
}
if strings.ToLower(u.Host) != "share" || u.User == nil {
return "", "" return "", ""
} }
id = u.User.Username()
password, _ = u.User.Password()
}
}
if len(id) > 32 || len(password) > 32 { return uri.ID(""), uri.Password()
return "", ""
} }
return id, password return id, password
} }
@ -143,7 +136,7 @@ func renderShareOGPage(c *gin.Context, dep dependency.Dep, id, password string)
SiteName: siteBasic.Name, SiteName: siteBasic.Name,
Title: siteBasic.Name, Title: siteBasic.Name,
Description: siteBasic.Description, Description: siteBasic.Description,
ShareURL: routes.MasterShareUrl(base, id, "").String(), ShareURL: routes.MasterShareUrl(base, id, password).String(),
RedirectURL: routes.MasterShareLongUrl(id, password).String(), RedirectURL: routes.MasterShareLongUrl(id, password).String(),
} }
@ -159,62 +152,66 @@ func renderShareOGPage(c *gin.Context, dep dependency.Dep, id, password string)
return renderOGHTML(data) return renderOGHTML(data)
} }
share, err := loadShareForOG(c, dep, shareID) shareInfo, err := loadShareForOG(c, shareID, password)
if err != nil { if err != nil {
if ent.IsNotFound(err) { var appErr serializer.AppError
data.Description = ogStatusInvalidLink if errors.As(err, &appErr) {
data.Description = appErr.Msg
} else { } else {
data.Description = ogStatusShareUnavailable data.Description = ogStatusInvalidLink
} }
return renderOGHTML(data) return renderOGHTML(data)
} }
if err := inventory.IsValidShare(share); err != nil { data.Title = shareInfo.Name
data.Description = ogStatusShareUnavailable if shareInfo.SourceType != nil && *shareInfo.SourceType == types.FileTypeFolder {
return renderOGHTML(data) data.Description = "Folder"
} else {
data.Description = formatFileSize(shareInfo.Size)
thumbnail, err := loadShareThumbnail(c, id, password, shareInfo)
if err == nil {
data.ImageURL = thumbnail
} }
if !canAnonymousAccessShare(c, dep) {
data.Description = ogStatusNeedLogin
return renderOGHTML(data)
} }
if share.Password != "" && subtle.ConstantTimeCompare([]byte(share.Password), []byte(password)) != 1 { data.Description += " · " + shareInfo.Owner.Nickname
data.Description = ogStatusPasswordRequired
return renderOGHTML(data) return renderOGHTML(data)
} }
targetFile := share.Edges.File func loadShareThumbnail(c *gin.Context, shareID, password string, shareInfo *explorer.Share) (string, error) {
if targetFile != nil { shareUri, err := fs.NewUriFromString(fs.NewShareUri(shareID, password))
fileName := targetFile.Name if err != nil {
fileType := types.FileType(targetFile.Type) return "", fmt.Errorf("failed to construct share uri: %w", err)
}
data.Title = fileName subService := &explorer.FileThumbService{
if fileType == types.FileTypeFolder { Uri: shareUri.Join(shareInfo.Name).String(),
data.Description = "Folder"
} else {
data.Description = formatFileSize(targetFile.Size)
} }
if share.Edges.User != nil && share.Edges.User.Nick != "" { if err := SetUserCtx(c, 0); err != nil {
data.Description += " · " + share.Edges.User.Nick return "", err
} }
res, err := subService.Get(c)
if err != nil {
return "", err
} }
return renderOGHTML(data) return res.Url, nil
} }
func loadShareForOG(c *gin.Context, dep dependency.Dep, shareID int) (*ent.Share, error) { func loadShareForOG(c *gin.Context, shareID int, password string) (*explorer.Share, error) {
ctx := inventory.WithShareEagerLoad(c) subService := &share.ShareInfoService{
return dep.ShareClient().GetByID(ctx, shareID) Password: password,
} CountViews: false,
}
func canAnonymousAccessShare(c *gin.Context, dep dependency.Dep) bool { if err := SetUserCtx(c, 0); err != nil {
anonymousGroup, err := dep.GroupClient().AnonymousGroup(c) return nil, err
if err != nil {
return false
} }
return anonymousGroup.Permissions.Enabled(int(types.GroupPermissionShareDownload))
util.WithValue(c, hashid.ObjectIDCtx{}, shareID)
return subService.Get(c)
} }
func renderOGHTML(data *ogData) string { func renderOGHTML(data *ogData) string {

@ -310,6 +310,7 @@ type Share struct {
Expired bool `json:"expired"` Expired bool `json:"expired"`
Url string `json:"url"` Url string `json:"url"`
ShowReadMe bool `json:"show_readme,omitempty"` ShowReadMe bool `json:"show_readme,omitempty"`
Size int64 `json:"size"`
// Only viewable by owner // Only viewable by owner
IsPrivate bool `json:"is_private,omitempty"` IsPrivate bool `json:"is_private,omitempty"`
@ -345,6 +346,10 @@ func BuildShare(s *ent.Share, base *url.URL, hasher hashid.Encoder, requester *e
res.Expires = s.Expires res.Expires = s.Expires
res.Password = s.Password res.Password = s.Password
res.ShowReadMe = s.Props != nil && s.Props.ShowReadMe res.ShowReadMe = s.Props != nil && s.Props.ShowReadMe
if t == types.FileTypeFile && s.Edges.File != nil {
res.Size = s.Edges.File.Size
}
} }
if requester.ID == owner.ID { if requester.ID == owner.ID {

Loading…
Cancel
Save