upload_helpers.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. package api
  2. import (
  3. "encoding/json"
  4. "mime/multipart"
  5. "net/http"
  6. acl "github.com/OliveTin/OliveTin/internal/acl"
  7. auth "github.com/OliveTin/OliveTin/internal/auth"
  8. authpublic "github.com/OliveTin/OliveTin/internal/auth/authpublic"
  9. config "github.com/OliveTin/OliveTin/internal/config"
  10. executor "github.com/OliveTin/OliveTin/internal/executor"
  11. log "github.com/sirupsen/logrus"
  12. )
  13. func (api *oliveTinAPI) uploadPrelude(w http.ResponseWriter, r *http.Request) bool {
  14. if r.Method != http.MethodPost {
  15. http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
  16. return false
  17. }
  18. if api.executor.UploadRegistry == nil {
  19. http.Error(w, "file uploads are not available", http.StatusServiceUnavailable)
  20. return false
  21. }
  22. return true
  23. }
  24. func uploadMaxBodyBytes(cfg *config.Config) int64 {
  25. maxBody := int64(cfg.FileUploads.MaxBytes)
  26. if maxBody <= 0 {
  27. maxBody = int64(config.DefaultFileUploadMaxBytes)
  28. }
  29. return maxBody
  30. }
  31. func (api *oliveTinAPI) parseUploadForm(w http.ResponseWriter, r *http.Request) bool {
  32. maxBody := uploadMaxBodyBytes(api.cfg)
  33. r.Body = http.MaxBytesReader(w, r.Body, maxBody+(1<<20))
  34. if err := r.ParseMultipartForm(32 << 20); err != nil {
  35. http.Error(w, "invalid multipart form", http.StatusBadRequest)
  36. return false
  37. }
  38. return true
  39. }
  40. func uploadFormIDs(r *http.Request) (string, string, bool) {
  41. bindingID := r.FormValue("binding_id")
  42. argName := r.FormValue("argument_name")
  43. if bindingID == "" || argName == "" {
  44. return "", "", false
  45. }
  46. return bindingID, argName, true
  47. }
  48. func (api *oliveTinAPI) uploadCleanupForm(r *http.Request) {
  49. if r.MultipartForm != nil {
  50. _ = r.MultipartForm.RemoveAll()
  51. }
  52. }
  53. func (api *oliveTinAPI) bindingForUpload(w http.ResponseWriter, bindingID string) *executor.ActionBinding {
  54. pair := api.executor.FindBindingByID(bindingID)
  55. if pair == nil || pair.Action == nil {
  56. http.Error(w, "action not found", http.StatusNotFound)
  57. return nil
  58. }
  59. return pair
  60. }
  61. func (api *oliveTinAPI) authorizeUploadRequest(w http.ResponseWriter, r *http.Request, bindingID, argName string) (*executor.ActionBinding, *config.ActionArgument) {
  62. user := auth.UserFromHTTPRequest(r, api.cfg)
  63. pair := api.bindingForUpload(w, bindingID)
  64. if pair == nil {
  65. return nil, nil
  66. }
  67. if !uploadExecAllowed(api, user, pair) {
  68. http.Error(w, "forbidden", http.StatusForbidden)
  69. return nil, nil
  70. }
  71. return uploadFileArgOrError(api, w, pair, argName)
  72. }
  73. func uploadExecAllowed(api *oliveTinAPI, user *authpublic.AuthenticatedUser, pair *executor.ActionBinding) bool {
  74. return acl.IsAllowedExec(api.cfg, user, pair.Action)
  75. }
  76. func uploadFileArgOrError(api *oliveTinAPI, w http.ResponseWriter, pair *executor.ActionBinding, argName string) (*executor.ActionBinding, *config.ActionArgument) {
  77. arg := api.findArgumentByName(pair.Action, argName)
  78. if arg == nil || arg.Type != "file_upload" {
  79. http.Error(w, "invalid file argument", http.StatusBadRequest)
  80. return nil, nil
  81. }
  82. return pair, arg
  83. }
  84. func (api *oliveTinAPI) openUploadedFormFile(w http.ResponseWriter, r *http.Request) (multipart.File, *multipart.FileHeader, bool) {
  85. file, hdr, err := r.FormFile("file")
  86. if err != nil {
  87. http.Error(w, "file field is required", http.StatusBadRequest)
  88. return nil, nil, false
  89. }
  90. return file, hdr, true
  91. }
  92. func (api *oliveTinAPI) tryProcessUpload(w http.ResponseWriter, r *http.Request) (string, bool) {
  93. bindingID, argName, ok := uploadFormIDs(r)
  94. if !ok {
  95. http.Error(w, "binding_id and argument_name are required", http.StatusBadRequest)
  96. return "", false
  97. }
  98. _, arg := api.authorizeUploadRequest(w, r, bindingID, argName)
  99. if arg == nil {
  100. return "", false
  101. }
  102. return api.stageUploadFromForm(w, r, bindingID, arg)
  103. }
  104. func (api *oliveTinAPI) stageUploadFromForm(w http.ResponseWriter, r *http.Request, bindingID string, arg *config.ActionArgument) (string, bool) {
  105. file, hdr, ok := api.openUploadedFormFile(w, r)
  106. if !ok {
  107. return "", false
  108. }
  109. defer file.Close()
  110. token, err := api.executor.UploadRegistry.StageFromMultipart(file, hdr.Filename, bindingID, arg)
  111. if err != nil {
  112. log.WithError(err).Warn("upload rejected")
  113. http.Error(w, "upload rejected", http.StatusBadRequest)
  114. return "", false
  115. }
  116. return token, true
  117. }
  118. func (api *oliveTinAPI) writeUploadTokenResponse(w http.ResponseWriter, token string) {
  119. w.Header().Set("Content-Type", "application/json")
  120. w.WriteHeader(http.StatusOK)
  121. if err := json.NewEncoder(w).Encode(map[string]string{"uploadToken": token}); err != nil {
  122. log.WithError(err).Warn("upload response encode failed")
  123. }
  124. }