Skip to content

Commit

Permalink
Add wopi discovery XML parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
ishank011 committed Jun 30, 2021
1 parent 0d6cb9d commit 1df0c59
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 24 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/Masterminds/sprig v2.22.0+incompatible
github.com/ReneKroon/ttlcache/v2 v2.7.0
github.com/aws/aws-sdk-go v1.38.68
github.com/beevik/etree v1.1.0
github.com/bluele/gcache v0.0.2
github.com/c-bata/go-prompt v0.2.5
github.com/cheggaaa/pb v1.0.29
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ github.com/aws/aws-sdk-go v1.38.68 h1:aOG8geU4SohNp659eKBHRBgbqSrZ6jNZlfimIuJAwL
github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/aws/aws-xray-sdk-go v0.9.4/go.mod h1:XtMKdBQfpVut+tJEwI7+dJFRxxRdxHDyVNp2tHXRq04=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
Expand Down
1 change: 1 addition & 0 deletions internal/http/services/appprovider/appprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ func (s *svc) Handler() http.Handler {
})
}

// WopiResponse holds the various fields to be returned for a wopi open call
type WopiResponse struct {
WopiClientURL string `json:"wopiclienturl"`
AccessToken string `json:"accesstoken"`
Expand Down
126 changes: 102 additions & 24 deletions pkg/app/provider/wopi/wopi.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,21 @@ import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"strings"
"time"

"github.com/cs3org/reva/pkg/app"

"github.com/beevik/etree"
appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/app"
"github.com/cs3org/reva/pkg/app/provider/registry"
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/mime"
"github.com/cs3org/reva/pkg/rhttp"
"github.com/cs3org/reva/pkg/user"
"github.com/mitchellh/mapstructure"
Expand Down Expand Up @@ -65,8 +67,7 @@ func parseConfig(m map[string]interface{}) (*config, error) {
type wopiProvider struct {
conf *config
wopiClient *http.Client
appEditURL string
appViewURL string
appURLs map[string]map[string]string // map[viewMode]map[extension]appURL
}

func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.ResourceInfo, viewMode appprovider.OpenInAppRequest_ViewMode, app string, token string) (string, error) {
Expand All @@ -77,11 +78,13 @@ func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.Resourc
return "", errors.New("AppProvider for " + p.conf.AppName + " cannot open in " + app)
}

ext := path.Ext(resource.Path)
wopiurl, err := url.Parse(p.conf.WopiURL)
if err != nil {
return "", err
}
wopiurl.Path = path.Join(wopiurl.Path, "/wopi/iop/openinapp")

httpReq, err := rhttp.NewRequest(ctx, "GET", wopiurl.String(), nil)
if err != nil {
return "", err
Expand All @@ -95,17 +98,20 @@ func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.Resourc
// or should be deprecated/removed altogether, needs discussion and decision.
// q.Add("folderurl", "...")
u, ok := user.ContextGetUser(ctx)
if ok {
if ok { // else defaults to "Anonymous Guest"
q.Add("username", u.Username)
}
// else defaults to "Anonymous Guest"

q.Add("appname", app)
q.Add("appurl", p.appEditURL)
q.Add("appurl", p.appURLs["edit"][ext])

if p.conf.AppIntURL != "" {
q.Add("appinturl", p.conf.AppIntURL)
}
if p.appViewURL != "" {
q.Add("appviewurl", p.appViewURL)
if viewExts, ok := p.appURLs["view"]; ok {
if url, ok := viewExts[ext]; ok {
q.Add("appviewurl", url)
}
}
if p.conf.AppAPIKey != "" {
httpReq.Header.Set("ApiKey", p.conf.AppAPIKey)
Expand Down Expand Up @@ -166,10 +172,10 @@ func New(m map[string]interface{}) (app.Provider, error) {
}
defer discRes.Body.Close()

var appEditURL string
var appViewURL string
var appURLs map[string]map[string]string

if discRes.StatusCode == http.StatusNotFound {
// this may be a bridge-supported app, let's find out
// this may be a bridge-supported app
discReq, err = http.NewRequest("GET", c.AppIntURL, nil)
if err != nil {
return nil, err
Expand All @@ -185,26 +191,45 @@ func New(m map[string]interface{}) (app.Provider, error) {
if err != nil {
return nil, err
}
bodyStr := buf.String()

// scrape app's home page to find the appname
if !strings.Contains(bodyStr, c.AppName) {
if !strings.Contains(buf.String(), c.AppName) {
// || (c.AppName != "CodiMD" && c.AppName != "Etherpad") {
return nil, errors.New("Application server at " + c.AppURL + " does not match this AppProvider for " + c.AppName)
}
appEditURL = c.AppURL
appViewURL = ""

// register the supported mimetypes in the AppRegistry: this is hardcoded for the time being
if c.AppName == "CodiMD" {
// TODO register `text/markdown` and `application/compressed-markdown`
appURLs = getCodimdExtensions(c.AppURL)
} else if c.AppName == "Etherpad" {
// TODO register some `application/compressed-pad` yet to be defined
appURLs = getEtherpadExtensions(c.AppURL)
}
} else if discRes.StatusCode == http.StatusOK {
// TODO parse XML from discRes.Body and populate appEditURL and appViewURL
appEditURL = ""
appViewURL = ""
// TODO register all supported mimetypes in the AppRegistry from the XML parsing
var netZoneName string
if c.AppName == "Collabora" {
netZoneName = "external-http"

} else if c.AppName == "Office Online" {
netZoneName = "external-https"
}
appURLs, err = parseWopiDiscovery(discRes.Body, netZoneName)
if err != nil {
return nil, errors.Wrap(err, "error parsing wopi discovery response")
}
}

// Initially we store the mime types in a map to avoid duplicates
mimeTypesMap := make(map[string]bool)
for _, extensions := range appURLs {
for ext := range extensions {
m := mime.Detect(false, ext)
mimeTypesMap[m] = true
}
}
// TODO register these mimetypes in the AppRegistry
mimeTypes := make([]string, 0, len(mimeTypesMap))
for m := range mimeTypesMap {
mimeTypes = append(mimeTypes, m)
}

wopiClient := rhttp.GetHTTPClient(
Expand All @@ -217,7 +242,60 @@ func New(m map[string]interface{}) (app.Provider, error) {
return &wopiProvider{
conf: c,
wopiClient: wopiClient,
appEditURL: appEditURL,
appViewURL: appViewURL,
appURLs: appURLs,
}, nil
}

func parseWopiDiscovery(body io.Reader, netZoneName string) (map[string]map[string]string, error) {
appURLs := make(map[string]map[string]string)

doc := etree.NewDocument()
if _, err := doc.ReadFrom(body); err != nil {
return nil, err
}
root := doc.SelectElement("wopi-discovery")

for _, netZone := range root.SelectElements("net-zone") {
nameAttr := netZone.SelectAttr("name")

if nameAttr.Value == netZoneName {
for _, app := range netZone.SelectElements("app") {
for _, action := range app.SelectElements("action") {
access := action.SelectAttrValue("name", "")
if access == "view" || access == "edit" {
ext := action.SelectAttrValue("ext", "")
url := action.SelectAttrValue("urlsrc", "")

if ext == "" || url == "" {
continue
}

if _, ok := appURLs[access]; !ok {
appURLs[access] = make(map[string]string)
}
appURLs[access][ext] = url
}
}
}
}
}
return appURLs, nil
}

func getCodimdExtensions(appURL string) map[string]map[string]string {
appURLs := make(map[string]map[string]string)
appURLs["edit"] = map[string]string{
"txt": appURL,
"md": appURL,
"zmd": appURL,
}
return appURLs
}

func getEtherpadExtensions(appURL string) map[string]map[string]string {
appURLs := make(map[string]map[string]string)
appURLs["edit"] = map[string]string{
"etherpad": appURL,
}
return appURLs
}

0 comments on commit 1df0c59

Please sign in to comment.