parent
42a31f2fd1
commit
c39daeb0d0
@ -1 +1 @@
|
|||||||
Subproject commit 01343d7656f839d13cc60adc6b56a238e3160465
|
Subproject commit d72688d7fceaa0bc916fc6637e928dfd5b5a1c43
|
@ -0,0 +1,77 @@
|
|||||||
|
package wopi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActonType string
|
||||||
|
|
||||||
|
var (
|
||||||
|
ActionPreview = ActonType("embedview")
|
||||||
|
ActionEdit = ActonType("edit")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DiscoverResponseCacheKey = "wopi_discover"
|
||||||
|
DiscoverRefreshDuration = 24 * 3600 // 24 hrs
|
||||||
|
)
|
||||||
|
|
||||||
|
// checkDiscovery checks if discovery content is needed to be refreshed.
|
||||||
|
// If so, it will refresh discovery content.
|
||||||
|
func (c *client) checkDiscovery() error {
|
||||||
|
c.mu.RLock()
|
||||||
|
if c.discovery == nil {
|
||||||
|
c.mu.RUnlock()
|
||||||
|
return c.refreshDiscovery()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.RUnlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// refresh Discovery action configs.
|
||||||
|
func (c *client) refreshDiscovery() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
cached, exist := cache.Get(DiscoverResponseCacheKey)
|
||||||
|
if exist {
|
||||||
|
cachedDiscovery := cached.(WopiDiscovery)
|
||||||
|
c.discovery = &cachedDiscovery
|
||||||
|
} else {
|
||||||
|
res, err := c.http.Request("GET", c.config.discoveryEndpoint.String(), nil).
|
||||||
|
CheckHTTPResponse(http.StatusOK).GetResponse()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to request discovery endpoint: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := xml.Unmarshal([]byte(res), &c.discovery); err != nil {
|
||||||
|
return fmt.Errorf("failed to parse response discovery endpoint: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cache.Set(DiscoverResponseCacheKey, *c.discovery, DiscoverRefreshDuration); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct actions map
|
||||||
|
c.actions = make(map[string]map[string]Action)
|
||||||
|
for _, app := range c.discovery.NetZone.App {
|
||||||
|
for _, action := range app.Action {
|
||||||
|
if action.Ext == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := c.actions["."+action.Ext]; !ok {
|
||||||
|
c.actions["."+action.Ext] = make(map[string]Action)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.actions["."+action.Ext][action.Name] = action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package wopi
|
||||||
|
|
||||||
|
import (
|
||||||
|
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||||
|
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
|
||||||
|
"github.com/cloudreve/Cloudreve/v3/pkg/request"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDiscovery(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
endpoint, _ := url.Parse("http://localhost:8001/hosting/discovery")
|
||||||
|
client := &client{
|
||||||
|
cache: cache.Store,
|
||||||
|
http: request.NewClient(),
|
||||||
|
config: config{
|
||||||
|
discoveryEndpoint: endpoint,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
a.NoError(client.refreshDiscovery())
|
||||||
|
client.NewSession(nil, &model.File{Name: "123.pptx"}, ActionPreview)
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package wopi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Response content from discovery endpoint.
|
||||||
|
type WopiDiscovery struct {
|
||||||
|
XMLName xml.Name `xml:"wopi-discovery"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
NetZone struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
App []struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
FavIconUrl string `xml:"favIconUrl,attr"`
|
||||||
|
BootstrapperUrl string `xml:"bootstrapperUrl,attr"`
|
||||||
|
AppBootstrapperUrl string `xml:"appBootstrapperUrl,attr"`
|
||||||
|
ApplicationBaseUrl string `xml:"applicationBaseUrl,attr"`
|
||||||
|
StaticResourceOrigin string `xml:"staticResourceOrigin,attr"`
|
||||||
|
CheckLicense string `xml:"checkLicense,attr"`
|
||||||
|
Action []Action `xml:"action"`
|
||||||
|
} `xml:"app"`
|
||||||
|
} `xml:"net-zone"`
|
||||||
|
ProofKey struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Oldvalue string `xml:"oldvalue,attr"`
|
||||||
|
Oldmodulus string `xml:"oldmodulus,attr"`
|
||||||
|
Oldexponent string `xml:"oldexponent,attr"`
|
||||||
|
Value string `xml:"value,attr"`
|
||||||
|
Modulus string `xml:"modulus,attr"`
|
||||||
|
Exponent string `xml:"exponent,attr"`
|
||||||
|
} `xml:"proof-key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Action struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
Ext string `xml:"ext,attr"`
|
||||||
|
Default string `xml:"default,attr"`
|
||||||
|
Urlsrc string `xml:"urlsrc,attr"`
|
||||||
|
Requires string `xml:"requires,attr"`
|
||||||
|
Targetext string `xml:"targetext,attr"`
|
||||||
|
Progid string `xml:"progid,attr"`
|
||||||
|
UseParent string `xml:"useParent,attr"`
|
||||||
|
Newprogid string `xml:"newprogid,attr"`
|
||||||
|
Newext string `xml:"newext,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gob.Register(WopiDiscovery{})
|
||||||
|
gob.Register(Action{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
package wopi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||||
|
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
|
||||||
|
"github.com/cloudreve/Cloudreve/v3/pkg/request"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client interface {
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
wopiSrcPlaceholder = "WOPI_SOURCE"
|
||||||
|
)
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
cache cache.Driver
|
||||||
|
http request.Client
|
||||||
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
discovery *WopiDiscovery
|
||||||
|
actions map[string]map[string]Action
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
discoveryEndpoint *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) NewSession(user *model.User, file *model.File, action ActonType) (*Session, error) {
|
||||||
|
if err := c.checkDiscovery(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ext := path.Ext(file.Name)
|
||||||
|
availableActions, ok := c.actions[ext]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrActionNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
actionConfig, ok := availableActions[string(action)]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrActionNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
actionUrl, err := generateActionUrl(actionConfig.Urlsrc, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(actionUrl)
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
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)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
queryReplaced.Set(k, queries.Get(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
actionUrl.RawQuery = queryReplaced.Encode()
|
||||||
|
return actionUrl, nil
|
||||||
|
}
|
Loading…
Reference in new issue