parent
4541400755
commit
1c922ac981
@ -0,0 +1,70 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/wopi"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
WopiSessionCtx = "wopi_session"
|
||||
)
|
||||
|
||||
// WopiWriteAccess validates if write access is obtained.
|
||||
func WopiWriteAccess() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
session := c.MustGet(WopiSessionCtx).(*wopi.SessionCache)
|
||||
if session.Action != wopi.ActionEdit {
|
||||
c.Status(http.StatusNotFound)
|
||||
c.Header(wopi.ServerErrorHeader, "read-only access")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func WopiAccessValidation(w wopi.Client, store cache.Driver) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
accessToken := strings.Split(c.Query(wopi.AccessTokenQuery), ".")
|
||||
if len(accessToken) != 2 {
|
||||
c.Status(http.StatusForbidden)
|
||||
c.Header(wopi.ServerErrorHeader, "malformed access token")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
sessionRaw, exist := store.Get(wopi.SessionCachePrefix + accessToken[0])
|
||||
if !exist {
|
||||
c.Status(http.StatusForbidden)
|
||||
c.Header(wopi.ServerErrorHeader, "invalid access token")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
session := sessionRaw.(wopi.SessionCache)
|
||||
user, err := model.GetActiveUserByID(session.UserID)
|
||||
if err != nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
c.Header(wopi.ServerErrorHeader, "user not found")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
fileID := c.MustGet("object_id").(uint)
|
||||
if fileID != session.FileID {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
c.Header(wopi.ServerErrorHeader, "file not found")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("user", &user)
|
||||
c.Set(WopiSessionCtx, &session)
|
||||
c.Next()
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/wopi"
|
||||
"github.com/cloudreve/Cloudreve/v3/service/explorer"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// CheckFileInfo Get file info
|
||||
func CheckFileInfo(c *gin.Context) {
|
||||
var service explorer.WopiService
|
||||
res, err := service.FileInfo(c)
|
||||
if err != nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
c.Header(wopi.ServerErrorHeader, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, res)
|
||||
}
|
||||
|
||||
// GetFile Get file content
|
||||
func GetFile(c *gin.Context) {
|
||||
var service explorer.WopiService
|
||||
err := service.GetFile(c)
|
||||
if err != nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
c.Header(wopi.ServerErrorHeader, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// PutFile Puts file content
|
||||
func PutFile(c *gin.Context) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
service := &explorer.FileIDService{}
|
||||
res := service.PutContent(ctx, c)
|
||||
switch res.Code {
|
||||
case serializer.CodeFileTooLarge:
|
||||
c.Status(http.StatusRequestEntityTooLarge)
|
||||
c.Header(wopi.ServerErrorHeader, res.Error)
|
||||
case serializer.CodeNotFound:
|
||||
c.Status(http.StatusNotFound)
|
||||
c.Header(wopi.ServerErrorHeader, res.Error)
|
||||
case 0:
|
||||
c.Status(http.StatusOK)
|
||||
default:
|
||||
c.Status(http.StatusInternalServerError)
|
||||
c.Header(wopi.ServerErrorHeader, res.Error)
|
||||
}
|
||||
}
|
||||
|
||||
// ModifyFile Modify file properties
|
||||
func ModifyFile(c *gin.Context) {
|
||||
action := c.GetHeader(wopi.OverwriteHeader)
|
||||
switch action {
|
||||
case wopi.MethodLock, wopi.MethodRefreshLock, wopi.MethodUnlock:
|
||||
c.Status(http.StatusOK)
|
||||
return
|
||||
case wopi.MethodRename:
|
||||
var service explorer.WopiService
|
||||
err := service.Rename(c)
|
||||
if err != nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
c.Header(wopi.ServerErrorHeader, err.Error())
|
||||
return
|
||||
}
|
||||
default:
|
||||
c.Status(http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
package explorer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/cloudreve/Cloudreve/v3/middleware"
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/wopi"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WopiService struct {
|
||||
}
|
||||
|
||||
func (service *WopiService) Rename(c *gin.Context) error {
|
||||
fs, _, err := service.prepareFs(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer fs.Recycle()
|
||||
|
||||
return fs.Rename(c, []uint{}, []uint{c.MustGet("object_id").(uint)}, c.GetHeader(wopi.RenameRequestHeader))
|
||||
}
|
||||
|
||||
func (service *WopiService) GetFile(c *gin.Context) error {
|
||||
fs, _, err := service.prepareFs(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer fs.Recycle()
|
||||
|
||||
resp, err := fs.Preview(c, fs.FileTarget[0].ID, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to pull file content: %w", err)
|
||||
}
|
||||
|
||||
// 重定向到文件源
|
||||
if resp.Redirect {
|
||||
return fmt.Errorf("redirect not supported in WOPI")
|
||||
}
|
||||
|
||||
// 直接返回文件内容
|
||||
defer resp.Content.Close()
|
||||
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
http.ServeContent(c.Writer, c.Request, fs.FileTarget[0].Name, fs.FileTarget[0].UpdatedAt, resp.Content)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *WopiService) FileInfo(c *gin.Context) (*serializer.WopiFileInfo, error) {
|
||||
fs, session, err := service.prepareFs(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer fs.Recycle()
|
||||
|
||||
parent, err := model.GetFoldersByIDs([]uint{fs.FileTarget[0].FolderID}, fs.User.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(parent) == 0 {
|
||||
return nil, fmt.Errorf("failed to find parent folder")
|
||||
}
|
||||
|
||||
parent[0].TraceRoot()
|
||||
siteUrl := model.GetSiteURL()
|
||||
|
||||
// Generate url for parent folder
|
||||
parentUrl := model.GetSiteURL()
|
||||
parentUrl.Path = "/home"
|
||||
query := parentUrl.Query()
|
||||
query.Set("path", parent[0].Position)
|
||||
parentUrl.RawQuery = query.Encode()
|
||||
|
||||
info := &serializer.WopiFileInfo{
|
||||
BaseFileName: fs.FileTarget[0].Name,
|
||||
Version: fs.FileTarget[0].Model.UpdatedAt.String(),
|
||||
BreadcrumbBrandName: model.GetSettingByName("siteName"),
|
||||
BreadcrumbBrandUrl: siteUrl.String(),
|
||||
FileSharingPostMessage: false,
|
||||
PostMessageOrigin: "*",
|
||||
FileNameMaxLength: 256,
|
||||
LastModifiedTime: fs.FileTarget[0].Model.UpdatedAt.Format(time.RFC3339),
|
||||
IsAnonymousUser: true,
|
||||
ReadOnly: true,
|
||||
ClosePostMessage: true,
|
||||
}
|
||||
|
||||
if session.Action == wopi.ActionEdit {
|
||||
info.FileSharingPostMessage = true
|
||||
info.IsAnonymousUser = false
|
||||
info.SupportsRename = true
|
||||
info.SupportsReviewing = true
|
||||
info.SupportsUpdate = true
|
||||
info.UserFriendlyName = fs.User.Nick
|
||||
info.UserId = hashid.HashID(fs.User.ID, hashid.UserID)
|
||||
info.UserCanRename = true
|
||||
info.UserCanReview = true
|
||||
info.UserCanWrite = true
|
||||
info.ReadOnly = false
|
||||
info.BreadcrumbFolderName = parent[0].Name
|
||||
info.BreadcrumbFolderUrl = parentUrl.String()
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (service *WopiService) prepareFs(c *gin.Context) (*filesystem.FileSystem, *wopi.SessionCache, error) {
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
session := c.MustGet(middleware.WopiSessionCtx).(*wopi.SessionCache)
|
||||
if err := fs.SetTargetFileByIDs([]uint{session.FileID}); err != nil {
|
||||
fs.Recycle()
|
||||
return nil, nil, fmt.Errorf("failed to find file: %w", err)
|
||||
}
|
||||
|
||||
maxSize := model.GetIntSetting("maxEditSize", 0)
|
||||
if maxSize > 0 && fs.FileTarget[0].Size > uint64(maxSize) {
|
||||
return nil, nil, errors.New("file too large")
|
||||
}
|
||||
|
||||
return fs, session, nil
|
||||
}
|
Loading…
Reference in new issue