s3api_object_handlers_postpolicy.go 7.7 KB

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