You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

240 lines
6.6 KiB

4 years ago
4 years ago
  1. package s3api
  2. import (
  3. "bytes"
  4. "encoding/base64"
  5. "errors"
  6. "github.com/chrislusf/seaweedfs/weed/s3api/policy"
  7. "github.com/chrislusf/seaweedfs/weed/s3api/s3err"
  8. "github.com/dustin/go-humanize"
  9. "github.com/gorilla/mux"
  10. "io"
  11. "io/ioutil"
  12. "mime/multipart"
  13. "net/http"
  14. "net/url"
  15. "strings"
  16. )
  17. func (s3a *S3ApiServer) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Request) {
  18. // https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
  19. // https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
  20. bucket := mux.Vars(r)["bucket"]
  21. reader, err := r.MultipartReader()
  22. if err != nil {
  23. writeErrorResponse(w, s3err.ErrMalformedPOSTRequest, r.URL)
  24. return
  25. }
  26. form, err := reader.ReadForm(int64(5 * humanize.MiByte))
  27. if err != nil {
  28. writeErrorResponse(w, s3err.ErrMalformedPOSTRequest, r.URL)
  29. return
  30. }
  31. defer form.RemoveAll()
  32. fileBody, fileName, fileSize, formValues, err := extractPostPolicyFormValues(form)
  33. if err != nil {
  34. writeErrorResponse(w, s3err.ErrMalformedPOSTRequest, r.URL)
  35. return
  36. }
  37. if fileBody == nil {
  38. writeErrorResponse(w, s3err.ErrPOSTFileRequired, r.URL)
  39. return
  40. }
  41. defer fileBody.Close()
  42. formValues.Set("Bucket", bucket)
  43. if fileName != "" && strings.Contains(formValues.Get("Key"), "${filename}") {
  44. formValues.Set("Key", strings.Replace(formValues.Get("Key"), "${filename}", fileName, -1))
  45. }
  46. object := formValues.Get("Key")
  47. successRedirect := formValues.Get("success_action_redirect")
  48. successStatus := formValues.Get("success_action_status")
  49. var redirectURL *url.URL
  50. if successRedirect != "" {
  51. redirectURL, err = url.Parse(successRedirect)
  52. if err != nil {
  53. writeErrorResponse(w, s3err.ErrMalformedPOSTRequest, r.URL)
  54. return
  55. }
  56. }
  57. // Verify policy signature.
  58. errCode := s3a.iam.doesPolicySignatureMatch(formValues)
  59. if errCode != s3err.ErrNone {
  60. writeErrorResponse(w, errCode, r.URL)
  61. return
  62. }
  63. policyBytes, err := base64.StdEncoding.DecodeString(formValues.Get("Policy"))
  64. if err != nil {
  65. writeErrorResponse(w, s3err.ErrMalformedPOSTRequest, r.URL)
  66. return
  67. }
  68. // Handle policy if it is set.
  69. if len(policyBytes) > 0 {
  70. postPolicyForm, err := policy.ParsePostPolicyForm(string(policyBytes))
  71. if err != nil {
  72. writeErrorResponse(w, s3err.ErrPostPolicyConditionInvalidFormat, r.URL)
  73. return
  74. }
  75. // Make sure formValues adhere to policy restrictions.
  76. if err = policy.CheckPostPolicy(formValues, postPolicyForm); err != nil {
  77. w.Header().Set("Location", r.URL.Path)
  78. w.WriteHeader(http.StatusTemporaryRedirect)
  79. return
  80. }
  81. // Ensure that the object size is within expected range, also the file size
  82. // should not exceed the maximum single Put size (5 GiB)
  83. lengthRange := postPolicyForm.Conditions.ContentLengthRange
  84. if lengthRange.Valid {
  85. if fileSize < lengthRange.Min {
  86. writeErrorResponse(w, s3err.ErrEntityTooSmall, r.URL)
  87. return
  88. }
  89. if fileSize > lengthRange.Max {
  90. writeErrorResponse(w, s3err.ErrEntityTooLarge, r.URL)
  91. return
  92. }
  93. }
  94. }
  95. uploadUrl := s3a.buildUploadUrl(bucket, object)
  96. etag, errCode := s3a.putToFiler(r, uploadUrl, fileBody)
  97. if errCode != s3err.ErrNone {
  98. writeErrorResponse(w, errCode, r.URL)
  99. return
  100. }
  101. if successRedirect != "" {
  102. // Replace raw query params..
  103. redirectURL.RawQuery = getRedirectPostRawQuery(bucket, object, etag)
  104. w.Header().Set("Location", redirectURL.String())
  105. writeResponse(w, http.StatusSeeOther, nil, mimeNone)
  106. return
  107. }
  108. setEtag(w, etag)
  109. // Decide what http response to send depending on success_action_status parameter
  110. switch successStatus {
  111. case "201":
  112. resp := encodeResponse(PostResponse{
  113. Bucket: bucket,
  114. Key: object,
  115. ETag: `"` + etag + `"`,
  116. Location: w.Header().Get("Location"),
  117. })
  118. writeResponse(w, http.StatusCreated, resp, mimeXML)
  119. case "200":
  120. writeResponse(w, http.StatusOK, nil, mimeNone)
  121. default:
  122. writeSuccessResponseEmpty(w)
  123. }
  124. }
  125. // Extract form fields and file data from a HTTP POST Policy
  126. func extractPostPolicyFormValues(form *multipart.Form) (filePart io.ReadCloser, fileName string, fileSize int64, formValues http.Header, err error) {
  127. /// HTML Form values
  128. fileName = ""
  129. // Canonicalize the form values into http.Header.
  130. formValues = make(http.Header)
  131. for k, v := range form.Value {
  132. formValues[http.CanonicalHeaderKey(k)] = v
  133. }
  134. // Validate form values.
  135. if err = validateFormFieldSize(formValues); err != nil {
  136. return nil, "", 0, nil, err
  137. }
  138. // this means that filename="" was not specified for file key and Go has
  139. // an ugly way of handling this situation. Refer here
  140. // https://golang.org/src/mime/multipart/formdata.go#L61
  141. if len(form.File) == 0 {
  142. var b = &bytes.Buffer{}
  143. for _, v := range formValues["File"] {
  144. b.WriteString(v)
  145. }
  146. fileSize = int64(b.Len())
  147. filePart = ioutil.NopCloser(b)
  148. return filePart, fileName, fileSize, formValues, nil
  149. }
  150. // Iterator until we find a valid File field and break
  151. for k, v := range form.File {
  152. canonicalFormName := http.CanonicalHeaderKey(k)
  153. if canonicalFormName == "File" {
  154. if len(v) == 0 {
  155. return nil, "", 0, nil, errors.New("Invalid arguments specified")
  156. }
  157. // Fetch fileHeader which has the uploaded file information
  158. fileHeader := v[0]
  159. // Set filename
  160. fileName = fileHeader.Filename
  161. // Open the uploaded part
  162. filePart, err = fileHeader.Open()
  163. if err != nil {
  164. return nil, "", 0, nil, err
  165. }
  166. // Compute file size
  167. fileSize, err = filePart.(io.Seeker).Seek(0, 2)
  168. if err != nil {
  169. return nil, "", 0, nil, err
  170. }
  171. // Reset Seek to the beginning
  172. _, err = filePart.(io.Seeker).Seek(0, 0)
  173. if err != nil {
  174. return nil, "", 0, nil, err
  175. }
  176. // File found and ready for reading
  177. break
  178. }
  179. }
  180. return filePart, fileName, fileSize, formValues, nil
  181. }
  182. // Validate form field size for s3 specification requirement.
  183. func validateFormFieldSize(formValues http.Header) error {
  184. // Iterate over form values
  185. for k := range formValues {
  186. // Check if value's field exceeds S3 limit
  187. if int64(len(formValues.Get(k))) > int64(1*humanize.MiByte) {
  188. return errors.New("Data size larger than expected")
  189. }
  190. }
  191. // Success.
  192. return nil
  193. }
  194. func getRedirectPostRawQuery(bucket, key, etag string) string {
  195. redirectValues := make(url.Values)
  196. redirectValues.Set("bucket", bucket)
  197. redirectValues.Set("key", key)
  198. redirectValues.Set("etag", "\""+etag+"\"")
  199. return redirectValues.Encode()
  200. }
  201. // Check to see if Policy is signed correctly.
  202. func (iam *IdentityAccessManagement) doesPolicySignatureMatch(formValues http.Header) s3err.ErrorCode {
  203. // For SignV2 - Signature field will be valid
  204. if _, ok := formValues["Signature"]; ok {
  205. return iam.doesPolicySignatureV2Match(formValues)
  206. }
  207. return iam.doesPolicySignatureV4Match(formValues)
  208. }