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.

241 lines
6.7 KiB

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