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.

244 lines
6.8 KiB

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