Skip to content

Commit

Permalink
add open-with-web endpoint (#3143)
Browse files Browse the repository at this point in the history
* add open-with-web endpoint

* reuse code instead of duplication

* add changelog

* switch to interface{}
  • Loading branch information
wkloucek committed Aug 17, 2022
1 parent 999fef2 commit 18c9e46
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 78 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Enhancement: Add /app/open-with-web endpoint

We've added an /app/open-with-web endpoint to the app provider, so that
clients that are no browser or have only limited browser access can also open apps with the help of a Web URL.

https://github.com/cs3org/reva/pull/3143
https://github.com/owncloud/ocis/pull/4376
228 changes: 155 additions & 73 deletions internal/http/services/appprovider/appprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package appprovider
import (
"encoding/json"
"net/http"
"net/url"
"path"

appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1"
Expand Down Expand Up @@ -52,6 +53,14 @@ type Config struct {
Prefix string `mapstructure:"prefix"`
GatewaySvc string `mapstructure:"gatewaysvc"`
Insecure bool `mapstructure:"insecure"`
WebBaseURI string `mapstructure:"webbaseuri"`
Web Web `mapstructure:"web"`
}

// Web holds the config options for the URL parameters for Web
type Web struct {
URLParamsMapping map[string]string `mapstructure:"urlparamsmapping"`
StaticURLParams map[string]string `mapstructure:"staticurlparams"`
}

func (c *Config) init() {
Expand Down Expand Up @@ -88,10 +97,16 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error)
return s, nil
}

const (
openModeNormal = iota
openModeWeb
)

func (s *svc) routerInit() error {
s.router.Get("/list", s.handleList)
s.router.Post("/new", s.handleNew)
s.router.Post("/open", s.handleOpen)
s.router.Post("/open", s.handleOpen(openModeNormal))
s.router.Post("/open-with-web", s.handleOpen(openModeWeb))
return nil
}

Expand Down Expand Up @@ -324,97 +339,164 @@ func (s *svc) handleList(w http.ResponseWriter, r *http.Request) {
}
}

func (s *svc) handleOpen(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
func (s *svc) handleOpen(openMode int) http.HandlerFunc {

client, err := pool.GetGatewayServiceClient(s.conf.GatewaySvc)
if err != nil {
writeError(w, r, appErrorServerError, "Internal error with the gateway, please try again later", err)
return
}
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

err = r.ParseForm()
if err != nil {
writeError(w, r, appErrorInvalidParameter, "parameters could not be parsed", nil)
}
client, err := pool.GetGatewayServiceClient(s.conf.GatewaySvc)
if err != nil {
writeError(w, r, appErrorServerError, "Internal error with the gateway, please try again later", err)
return
}

fileID := r.Form.Get("file_id")
err = r.ParseForm()
if err != nil {
writeError(w, r, appErrorInvalidParameter, "parameters could not be parsed", nil)
}

if fileID == "" {
writeError(w, r, appErrorInvalidParameter, "missing file ID", nil)
return
}
fileID := r.Form.Get("file_id")

resourceID, err := storagespace.ParseID(fileID)
if err != nil {
writeError(w, r, appErrorInvalidParameter, "invalid file ID", nil)
return
}
if fileID == "" {
writeError(w, r, appErrorInvalidParameter, "missing file ID", nil)
return
}

fileRef := &provider.Reference{
ResourceId: &resourceID,
Path: ".",
}
resourceID, err := storagespace.ParseID(fileID)
if err != nil {
writeError(w, r, appErrorInvalidParameter, "invalid file ID", nil)
return
}

statRes, err := client.Stat(ctx, &provider.StatRequest{Ref: fileRef})
if err != nil {
writeError(w, r, appErrorServerError, "Internal error accessing the file, please try again later", err)
return
}
fileRef := &provider.Reference{
ResourceId: &resourceID,
Path: ".",
}

if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND {
writeError(w, r, appErrorNotFound, "file does not exist", nil)
return
} else if statRes.Status.Code != rpc.Code_CODE_OK {
writeError(w, r, appErrorServerError, "failed to stat the file", nil)
return
}
statRes, err := client.Stat(ctx, &provider.StatRequest{Ref: fileRef})
if err != nil {
writeError(w, r, appErrorServerError, "Internal error accessing the file, please try again later", err)
return
}

if statRes.Info.Type != provider.ResourceType_RESOURCE_TYPE_FILE {
writeError(w, r, appErrorInvalidParameter, "the given file id does not point to a file", nil)
return
}
if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND {
writeError(w, r, appErrorNotFound, "file does not exist", nil)
return
} else if statRes.Status.Code != rpc.Code_CODE_OK {
writeError(w, r, appErrorServerError, "failed to stat the file", nil)
return
}

viewMode := getViewMode(statRes.Info, r.Form.Get("view_mode"))
if viewMode == gateway.OpenInAppRequest_VIEW_MODE_INVALID {
writeError(w, r, appErrorInvalidParameter, "invalid view mode", err)
return
}
if statRes.Info.Type != provider.ResourceType_RESOURCE_TYPE_FILE {
writeError(w, r, appErrorInvalidParameter, "the given file id does not point to a file", nil)
return
}

openReq := gateway.OpenInAppRequest{
Ref: fileRef,
ViewMode: viewMode,
App: r.Form.Get("app_name"),
}
openRes, err := client.OpenInApp(ctx, &openReq)
if err != nil {
writeError(w, r, appErrorServerError,
"Error contacting the requested application, please use a different one or try again later", err)
return
}
if openRes.Status.Code != rpc.Code_CODE_OK {
if openRes.Status.Code == rpc.Code_CODE_NOT_FOUND {
writeError(w, r, appErrorNotFound, openRes.Status.Message, nil)
viewMode := getViewMode(statRes.Info, r.Form.Get("view_mode"))
if viewMode == gateway.OpenInAppRequest_VIEW_MODE_INVALID {
writeError(w, r, appErrorInvalidParameter, "invalid view mode", err)
return
}

openReq := gateway.OpenInAppRequest{
Ref: fileRef,
ViewMode: viewMode,
App: r.Form.Get("app_name"),
}
openRes, err := client.OpenInApp(ctx, &openReq)
if err != nil {
writeError(w, r, appErrorServerError,
"Error contacting the requested application, please use a different one or try again later", err)
return
}
if openRes.Status.Code != rpc.Code_CODE_OK {
if openRes.Status.Code == rpc.Code_CODE_NOT_FOUND {
writeError(w, r, appErrorNotFound, openRes.Status.Message, nil)
return
}
writeError(w, r, appErrorServerError, openRes.Status.Message,
status.NewErrorFromCode(openRes.Status.Code, "error calling OpenInApp"))
return
}

var payload interface{}

switch openMode {
case openModeNormal:
payload = openRes.AppUrl

case openModeWeb:
payload, err = newOpenInWebResponse(s.conf.WebBaseURI, s.conf.Web.URLParamsMapping, s.conf.Web.StaticURLParams, fileID, r.Form.Get("app_name"), r.Form.Get("view_mode"))
if err != nil {
writeError(w, r, appErrorServerError, "Internal error",
errors.Wrap(err, "error building OpenInWeb response"))
return
}

default:
writeError(w, r, appErrorServerError, "Internal error with the open mode",
errors.New("unknown open mode"))
return

}

js, err := json.Marshal(payload)
if err != nil {
writeError(w, r, appErrorServerError, "Internal error with JSON payload",
errors.Wrap(err, "error marshalling JSON response"))
return
}

w.Header().Set("Content-Type", "application/json")
if _, err = w.Write(js); err != nil {
writeError(w, r, appErrorServerError, "Internal error with JSON payload",
errors.Wrap(err, "error writing JSON response"))
return
}
writeError(w, r, appErrorServerError, openRes.Status.Message,
status.NewErrorFromCode(openRes.Status.Code, "error calling OpenInApp"))
return
}
}

type openInWebResponse struct {
URI string `json:"uri"`
}

func newOpenInWebResponse(baseURI string, params, staticParams map[string]string, fileID, appName, viewMode string) (openInWebResponse, error) {

js, err := json.Marshal(openRes.AppUrl)
uri, err := url.Parse(baseURI)
if err != nil {
writeError(w, r, appErrorServerError, "Internal error with JSON payload",
errors.Wrap(err, "error marshalling JSON response"))
return
return openInWebResponse{}, err
}

w.Header().Set("Content-Type", "application/json")
if _, err = w.Write(js); err != nil {
writeError(w, r, appErrorServerError, "Internal error with JSON payload",
errors.Wrap(err, "error writing JSON response"))
return
query := uri.Query()

for key, val := range params {

switch val {
case "fileid":
if fileID != "" {
query.Add(key, fileID)
}
case "appname":
if appName != "" {
query.Add(key, appName)
}
case "viewmode":
if viewMode != "" {
query.Add(key, viewMode)
}
default:
return openInWebResponse{}, errors.New("unknown parameter mapper")
}

}

for key, val := range staticParams {
query.Add(key, val)
}

uri.RawQuery = query.Encode()

return openInWebResponse{URI: uri.String()}, nil
}

func filterAppsByUserAgent(mimeTypes []*appregistry.MimeTypeInfo, userAgent string) []*appregistry.MimeTypeInfo {
Expand Down
11 changes: 6 additions & 5 deletions internal/http/services/owncloud/ocs/data/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,12 @@ type CapabilitiesArchiver struct {

// CapabilitiesAppProvider holds available app provider information
type CapabilitiesAppProvider struct {
Enabled bool `json:"enabled" xml:"enabled" mapstructure:"enabled"`
Version string `json:"version" xml:"version" mapstructure:"version"`
AppsURL string `json:"apps_url" xml:"apps_url" mapstructure:"apps_url"`
OpenURL string `json:"open_url" xml:"open_url" mapstructure:"open_url"`
NewURL string `json:"new_url" xml:"new_url" mapstructure:"new_url"`
Enabled bool `json:"enabled" xml:"enabled" mapstructure:"enabled"`
Version string `json:"version" xml:"version" mapstructure:"version"`
AppsURL string `json:"apps_url" xml:"apps_url" mapstructure:"apps_url"`
OpenURL string `json:"open_url" xml:"open_url" mapstructure:"open_url"`
OpenWebURL string `json:"open_web_url" xml:"open_web_url" mapstructure:"open_web_url"`
NewURL string `json:"new_url" xml:"new_url" mapstructure:"new_url"`
}

// CapabilitiesFiles TODO this is storage specific, not global. What effect do these options have on the clients?
Expand Down

0 comments on commit 18c9e46

Please sign in to comment.