github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/office/office.go (about) 1 package office 2 3 import ( 4 "net/http" 5 "os" 6 "strconv" 7 "strings" 8 9 "github.com/cozy/cozy-stack/model/office" 10 "github.com/cozy/cozy-stack/model/permission" 11 "github.com/cozy/cozy-stack/model/sharing" 12 "github.com/cozy/cozy-stack/model/vfs" 13 "github.com/cozy/cozy-stack/pkg/consts" 14 "github.com/cozy/cozy-stack/pkg/jsonapi" 15 "github.com/cozy/cozy-stack/web/files" 16 "github.com/cozy/cozy-stack/web/middlewares" 17 "github.com/labstack/echo/v4" 18 ) 19 20 // Open returns the parameters to open an office document. 21 func Open(c echo.Context) error { 22 inst := middlewares.GetInstance(c) 23 fileID := c.Param("id") 24 open, err := sharing.OpenOffice(inst, fileID) 25 if err != nil { 26 return wrapError(err) 27 } 28 pdoc, err := middlewares.GetPermission(c) 29 if err != nil { 30 return err 31 } 32 memberIndex, _ := strconv.Atoi(c.QueryParam("MemberIndex")) 33 readOnly := c.QueryParam("ReadOnly") == "true" 34 35 // If a directory is shared by link and contains an office document, the 36 // document can be opened with the same sharecode as the directory. The 37 // sharecode is also used to identify the member that previews a sharing. 38 if pdoc.Type == permission.TypeShareByLink || pdoc.Type == permission.TypeSharePreview { 39 code := middlewares.GetRequestToken(c) 40 open.AddShareByLinkCode(code) 41 if !readOnly { 42 readOnly = true 43 for _, perm := range pdoc.Permissions { 44 if perm.Type == consts.Files && !perm.Verbs.ReadOnly() { 45 readOnly = false 46 } 47 } 48 } 49 } 50 51 sharingID := c.QueryParam("SharingID") // Cozy to Cozy sharing 52 if err := open.CheckPermission(pdoc, sharingID); err != nil { 53 return middlewares.ErrForbidden 54 } 55 56 doc, err := open.GetResult(memberIndex, readOnly) 57 if err != nil { 58 return wrapError(err) 59 } 60 61 return jsonapi.Data(c, http.StatusOK, doc, nil) 62 } 63 64 func FileByKey(c echo.Context) error { 65 inst := middlewares.GetInstance(c) 66 file, err := office.EnsureFileForKey(inst, c.Param("key")) 67 if err != nil { 68 return wrapError(err) 69 } 70 if err := middlewares.AllowVFS(c, permission.GET, file); err != nil { 71 return err 72 } 73 doc := files.NewFile(file, inst) 74 return jsonapi.Data(c, http.StatusOK, doc, nil) 75 } 76 77 // Callback is the handler for OnlyOffice callback requests. 78 // Cf https://api.onlyoffice.com/editors/callback 79 func Callback(c echo.Context) error { 80 inst := middlewares.GetInstance(c) 81 var params office.CallbackParameters 82 if err := c.Bind(¶ms); err != nil { 83 inst.Logger().WithNamespace("office"). 84 Warnf("Cannot bind callback parameters: %s", err) 85 return c.JSON(http.StatusBadRequest, echo.Map{"error": "Invalid request"}) 86 } 87 header := c.Request().Header.Get(echo.HeaderAuthorization) 88 params.Token = strings.TrimPrefix(header, "Bearer ") 89 90 if err := office.Callback(inst, params); err != nil { 91 inst.Logger().WithNamespace("office"). 92 Infof("Error on the callback: %s", err) 93 code := http.StatusInternalServerError 94 if httpError, ok := err.(*echo.HTTPError); ok { 95 code = httpError.Code 96 } 97 return c.JSON(code, echo.Map{"error": err.Error()}) 98 } 99 return c.JSON(http.StatusOK, echo.Map{"error": 0}) 100 } 101 102 // Routes sets the routing for the collaborative edition of office documents. 103 func Routes(router *echo.Group) { 104 router.GET("/:id/open", Open) 105 router.POST("/keys/:key", FileByKey) 106 router.POST("/callback", Callback) 107 } 108 109 func wrapError(err error) *jsonapi.Error { 110 switch err { 111 case office.ErrNoServer, office.ErrInvalidFile, sharing.ErrCannotOpenFile: 112 return jsonapi.NotFound(err) 113 case office.ErrInternalServerError: 114 return jsonapi.InternalServerError(err) 115 case office.ErrInvalidKey: 116 return jsonapi.NotFound(err) 117 case os.ErrNotExist, vfs.ErrParentDoesNotExist, vfs.ErrParentInTrash: 118 return jsonapi.NotFound(err) 119 case sharing.ErrMemberNotFound: 120 return jsonapi.NotFound(err) 121 } 122 return jsonapi.InternalServerError(err) 123 }