mirror of https://github.com/cloudreve/Cloudreve
134 lines
3.6 KiB
Go
134 lines
3.6 KiB
Go
package wopi
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/cloudreve/Cloudreve/v4/inventory/types"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cloudreve/Cloudreve/v4/application/dependency"
|
|
"github.com/cloudreve/Cloudreve/v4/pkg/cluster/routes"
|
|
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager"
|
|
"github.com/cloudreve/Cloudreve/v4/pkg/hashid"
|
|
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
|
)
|
|
|
|
var (
|
|
ErrActionNotSupported = errors.New("action not supported by current wopi endpoint")
|
|
|
|
queryPlaceholders = map[string]string{
|
|
"BUSINESS_USER": "",
|
|
"DC_LLCC": "lng",
|
|
"DISABLE_ASYNC": "",
|
|
"DISABLE_CHAT": "",
|
|
"EMBEDDED": "true",
|
|
"FULLSCREEN": "true",
|
|
"HOST_SESSION_ID": "",
|
|
"SESSION_CONTEXT": "",
|
|
"RECORDING": "",
|
|
"THEME_ID": "darkmode",
|
|
"UI_LLCC": "lng",
|
|
"VALIDATOR_TEST_CATEGORY": "",
|
|
}
|
|
)
|
|
|
|
const (
|
|
SessionCachePrefix = "wopi_session_"
|
|
AccessTokenQuery = "access_token"
|
|
OverwriteHeader = WopiHeaderPrefix + "Override"
|
|
ServerErrorHeader = WopiHeaderPrefix + "ServerError"
|
|
RenameRequestHeader = WopiHeaderPrefix + "RequestedName"
|
|
LockTokenHeader = WopiHeaderPrefix + "Lock"
|
|
ItemVersionHeader = WopiHeaderPrefix + "ItemVersion"
|
|
SuggestedTargetHeader = WopiHeaderPrefix + "SuggestedTarget"
|
|
|
|
MethodLock = "LOCK"
|
|
MethodUnlock = "UNLOCK"
|
|
MethodRefreshLock = "REFRESH_LOCK"
|
|
MethodPutRelative = "PUT_RELATIVE"
|
|
wopiSrcPlaceholder = "WOPI_SOURCE"
|
|
wopiSrcParamDefault = "WOPISrc"
|
|
languageParamDefault = "lang"
|
|
WopiHeaderPrefix = "X-WOPI-"
|
|
|
|
LockDuration = time.Duration(30) * time.Minute
|
|
)
|
|
|
|
func GenerateWopiSrc(ctx context.Context, action types.ViewerAction, viewer *types.Viewer, viewerSession *manager.ViewerSession) (*url.URL, error) {
|
|
dep := dependency.FromContext(ctx)
|
|
base := dep.SettingProvider().SiteURL(setting.UseFirstSiteUrl(ctx))
|
|
hasher := dep.HashIDEncoder()
|
|
|
|
availableActions, ok := viewer.WopiActions[viewerSession.File.Ext()]
|
|
if !ok {
|
|
return nil, ErrActionNotSupported
|
|
}
|
|
|
|
var (
|
|
src string
|
|
)
|
|
fallbackOrder := []types.ViewerAction{action, types.ViewerActionView, types.ViewerActionEdit}
|
|
for _, a := range fallbackOrder {
|
|
if src, ok = availableActions[a]; ok {
|
|
break
|
|
}
|
|
}
|
|
|
|
if src == "" {
|
|
return nil, ErrActionNotSupported
|
|
}
|
|
|
|
actionUrl, err := generateActionUrl(src,
|
|
routes.MasterWopiSrc(base, hashid.EncodeFileID(hasher, viewerSession.File.ID())).String())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return actionUrl, nil
|
|
}
|
|
|
|
// Replace query parameters in action URL template. Some placeholders need to be replaced
|
|
// at the frontend, e.g. `THEME_ID`.
|
|
func generateActionUrl(src string, fileSrc string) (*url.URL, error) {
|
|
src = strings.ReplaceAll(src, "<", "")
|
|
src = strings.ReplaceAll(src, ">", "")
|
|
actionUrl, err := url.Parse(src)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse action url: %s", err)
|
|
}
|
|
|
|
queries := actionUrl.Query()
|
|
srcReplaced := false
|
|
queryReplaced := url.Values{}
|
|
for k := range queries {
|
|
if placeholder, ok := queryPlaceholders[queries.Get(k)]; ok {
|
|
if placeholder != "" {
|
|
queryReplaced.Set(k, placeholder)
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
if queries.Get(k) == wopiSrcPlaceholder {
|
|
queryReplaced.Set(k, fileSrc)
|
|
srcReplaced = true
|
|
continue
|
|
}
|
|
|
|
queryReplaced.Set(k, queries.Get(k))
|
|
}
|
|
|
|
if !srcReplaced {
|
|
queryReplaced.Set(wopiSrcParamDefault, fileSrc)
|
|
}
|
|
|
|
// LibreOffice require this flag to show correct language
|
|
queryReplaced.Set(languageParamDefault, "lng")
|
|
|
|
actionUrl.RawQuery = queryReplaced.Encode()
|
|
return actionUrl, nil
|
|
}
|