mirror of
https://github.com/minio/minio.git
synced 2025-01-11 15:03:22 -05:00
POSTForm: Return http 303 if redirect is specified (#3479)
success_action_redirect in the sent Form means that the server needs to return 303 in addition to a well specific redirection url, this commit adds this feature
This commit is contained in:
parent
faa6b1e925
commit
d8e4d3c9c8
@ -492,17 +492,23 @@ func writeResponse(w http.ResponseWriter, statusCode int, response []byte) {
|
|||||||
w.(http.Flusher).Flush()
|
w.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeSuccessResponse write success headers and response if any.
|
// writeSuccessResponse writes success headers and response if any.
|
||||||
func writeSuccessResponse(w http.ResponseWriter, response []byte) {
|
func writeSuccessResponse(w http.ResponseWriter, response []byte) {
|
||||||
writeResponse(w, http.StatusOK, response)
|
writeResponse(w, http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeSuccessNoContent write success headers with http status 204
|
// writeSuccessNoContent writes success headers with http status 204
|
||||||
func writeSuccessNoContent(w http.ResponseWriter) {
|
func writeSuccessNoContent(w http.ResponseWriter) {
|
||||||
writeResponse(w, http.StatusNoContent, nil)
|
writeResponse(w, http.StatusNoContent, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeErrorRespone write error headers
|
// writeRedirectSeeOther writes Location header with http status 303
|
||||||
|
func writeRedirectSeeOther(w http.ResponseWriter, location string) {
|
||||||
|
w.Header().Set("Location", location)
|
||||||
|
writeResponse(w, http.StatusSeeOther, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeErrorRespone writes error headers
|
||||||
func writeErrorResponse(w http.ResponseWriter, req *http.Request, errorCode APIErrorCode, resource string) {
|
func writeErrorResponse(w http.ResponseWriter, req *http.Request, errorCode APIErrorCode, resource string) {
|
||||||
apiError := getAPIError(errorCode)
|
apiError := getAPIError(errorCode)
|
||||||
// set common headers
|
// set common headers
|
||||||
|
@ -19,6 +19,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -447,21 +448,37 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"")
|
w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"")
|
||||||
w.Header().Set("Location", getObjectLocation(bucket, object))
|
w.Header().Set("Location", getObjectLocation(bucket, object))
|
||||||
|
|
||||||
// Decide what http response to send depending on success_action_status parameter
|
successRedirect := formValues[http.CanonicalHeaderKey("success_action_redirect")]
|
||||||
switch formValues[http.CanonicalHeaderKey("success_action_status")] {
|
successStatus := formValues[http.CanonicalHeaderKey("success_action_status")]
|
||||||
case "201":
|
|
||||||
resp := encodeResponse(PostResponse{
|
|
||||||
Bucket: bucket,
|
|
||||||
Key: object,
|
|
||||||
ETag: "\"" + objInfo.MD5Sum + "\"",
|
|
||||||
Location: getObjectLocation(bucket, object),
|
|
||||||
})
|
|
||||||
writeResponse(w, http.StatusCreated, resp)
|
|
||||||
|
|
||||||
case "200":
|
if successStatus == "" && successRedirect == "" {
|
||||||
writeSuccessResponse(w, nil)
|
|
||||||
default:
|
|
||||||
writeSuccessNoContent(w)
|
writeSuccessNoContent(w)
|
||||||
|
} else {
|
||||||
|
if successRedirect != "" {
|
||||||
|
redirectURL := successRedirect + "?" + fmt.Sprintf("bucket=%s&key=%s&etag=%s",
|
||||||
|
bucket,
|
||||||
|
getURLEncodedName(object),
|
||||||
|
getURLEncodedName("\""+objInfo.MD5Sum+"\""))
|
||||||
|
|
||||||
|
writeRedirectSeeOther(w, redirectURL)
|
||||||
|
} else {
|
||||||
|
// Decide what http response to send depending on success_action_status parameter
|
||||||
|
switch successStatus {
|
||||||
|
case "201":
|
||||||
|
resp := encodeResponse(PostResponse{
|
||||||
|
Bucket: bucket,
|
||||||
|
Key: object,
|
||||||
|
ETag: "\"" + objInfo.MD5Sum + "\"",
|
||||||
|
Location: getObjectLocation(bucket, object),
|
||||||
|
})
|
||||||
|
writeResponse(w, http.StatusCreated, resp)
|
||||||
|
|
||||||
|
case "200":
|
||||||
|
writeSuccessResponse(w, nil)
|
||||||
|
default:
|
||||||
|
writeSuccessNoContent(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify object created event.
|
// Notify object created event.
|
||||||
|
@ -326,10 +326,10 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
|
|||||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
// policy := buildGenericPolicy(curTime, testCase.accessKey, bucketName, testCase.objectName, false)
|
|
||||||
testCase.policy = fmt.Sprintf(testCase.policy, testCase.dates...)
|
testCase.policy = fmt.Sprintf(testCase.policy, testCase.dates...)
|
||||||
|
|
||||||
req, perr := newPostRequestV4Generic("", bucketName, testCase.objectName, testCase.data, testCase.accessKey,
|
req, perr := newPostRequestV4Generic("", bucketName, testCase.objectName, testCase.data, testCase.accessKey,
|
||||||
testCase.secretKey, curTime, []byte(testCase.policy), testCase.corruptedBase64, testCase.corruptedMultipart)
|
testCase.secretKey, curTime, []byte(testCase.policy), nil, testCase.corruptedBase64, testCase.corruptedMultipart)
|
||||||
if perr != nil {
|
if perr != nil {
|
||||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for PostPolicyHandler: <ERROR> %v", i+1, instanceType, perr)
|
t.Fatalf("Test %d: %s: Failed to create HTTP request for PostPolicyHandler: <ERROR> %v", i+1, instanceType, perr)
|
||||||
}
|
}
|
||||||
@ -395,6 +395,93 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wrapper for calling TestPostPolicyBucketHandlerRedirect tests for both XL multiple disks and single node setup.
|
||||||
|
func TestPostPolicyBucketHandlerRedirect(t *testing.T) {
|
||||||
|
ExecObjectLayerTest(t, testPostPolicyBucketHandlerRedirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
// testPostPolicyBucketHandlerRedirect tests POST Object when success_action_redirect is specified
|
||||||
|
func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t TestErrHandler) {
|
||||||
|
root, err := newTestConfig("us-east-1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Initializing config.json failed")
|
||||||
|
}
|
||||||
|
defer removeAll(root)
|
||||||
|
|
||||||
|
// Register event notifier.
|
||||||
|
err = initEventNotifier(obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Initializing event notifiers failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// get random bucket name.
|
||||||
|
bucketName := getRandomBucketName()
|
||||||
|
|
||||||
|
// Key specified in Form data
|
||||||
|
keyName := "test/object"
|
||||||
|
|
||||||
|
// The final name of the upload object
|
||||||
|
targetObj := keyName + "/upload.txt"
|
||||||
|
|
||||||
|
// The url of success_action_redirect field
|
||||||
|
redirectURL := "http://www.google.com"
|
||||||
|
|
||||||
|
// Register the API end points with XL/FS object layer.
|
||||||
|
apiRouter := initTestAPIEndPoints(obj, []string{"PostPolicy"})
|
||||||
|
|
||||||
|
credentials := serverConfig.GetCredential()
|
||||||
|
|
||||||
|
curTime := time.Now().UTC()
|
||||||
|
curTimePlus5Min := curTime.Add(time.Minute * 5)
|
||||||
|
|
||||||
|
err = obj.MakeBucket(bucketName)
|
||||||
|
if err != nil {
|
||||||
|
// Failed to create newbucket, abort.
|
||||||
|
t.Fatalf("%s : %s", instanceType, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
dates := []interface{}{curTimePlus5Min.Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}
|
||||||
|
policy := `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], {"success_action_redirect":"` + redirectURL + `"},["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKeyID + `/%s/us-east-1/s3/aws4_request"]]}`
|
||||||
|
|
||||||
|
// Generate the final policy document
|
||||||
|
policy = fmt.Sprintf(policy, dates...)
|
||||||
|
|
||||||
|
// Create a new POST request with success_action_redirect field specified
|
||||||
|
req, perr := newPostRequestV4Generic("", bucketName, keyName, []byte("objData"),
|
||||||
|
credentials.AccessKeyID, credentials.SecretAccessKey, curTime,
|
||||||
|
[]byte(policy), map[string]string{"success_action_redirect": redirectURL}, false, false)
|
||||||
|
|
||||||
|
if perr != nil {
|
||||||
|
t.Fatalf("%s: Failed to create HTTP request for PostPolicyHandler: <ERROR> %v", instanceType, perr)
|
||||||
|
}
|
||||||
|
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||||
|
// Call the ServeHTTP to execute the handler.
|
||||||
|
apiRouter.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
// Check the status code, which must be 303 because success_action_redirect is specified
|
||||||
|
if rec.Code != http.StatusSeeOther {
|
||||||
|
t.Errorf("%s: Expected the response status to be `%d`, but instead found `%d`", instanceType, http.StatusSeeOther, rec.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the uploaded object info
|
||||||
|
info, err := obj.GetObjectInfo(bucketName, targetObj)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Unexpected error: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedLocation := fmt.Sprintf(redirectURL+"?bucket=%s&key=%s&etag=%s",
|
||||||
|
bucketName, getURLEncodedName(targetObj), getURLEncodedName("\""+info.MD5Sum+"\""))
|
||||||
|
|
||||||
|
// Check the new location url
|
||||||
|
if rec.HeaderMap.Get("Location") != expectedLocation {
|
||||||
|
t.Errorf("Unexpected location, expected = %s, found = `%s`", rec.HeaderMap.Get("Location"), expectedLocation)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// postPresignSignatureV4 - presigned signature for PostPolicy requests.
|
// postPresignSignatureV4 - presigned signature for PostPolicy requests.
|
||||||
func postPresignSignatureV4(policyBase64 string, t time.Time, secretAccessKey, location string) string {
|
func postPresignSignatureV4(policyBase64 string, t time.Time, secretAccessKey, location string) string {
|
||||||
// Get signining key.
|
// Get signining key.
|
||||||
@ -467,7 +554,8 @@ func buildGenericPolicy(t time.Time, accessKey, bucketName, objectName string, c
|
|||||||
return policy
|
return policy
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string, t time.Time, policy []byte, corruptedB64 bool, corruptedMultipart bool) (*http.Request, error) {
|
func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string,
|
||||||
|
t time.Time, policy []byte, addFormData map[string]string, corruptedB64 bool, corruptedMultipart bool) (*http.Request, error) {
|
||||||
// Get the user credential.
|
// Get the user credential.
|
||||||
credStr := getCredential(accessKey, serverConfig.GetRegion(), t)
|
credStr := getCredential(accessKey, serverConfig.GetRegion(), t)
|
||||||
|
|
||||||
@ -493,6 +581,11 @@ func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData []
|
|||||||
"Content-Encoding": "gzip",
|
"Content-Encoding": "gzip",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add form data
|
||||||
|
for k, v := range addFormData {
|
||||||
|
formData[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
// Create the multipart form.
|
// Create the multipart form.
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
w := multipart.NewWriter(&buf)
|
w := multipart.NewWriter(&buf)
|
||||||
@ -529,11 +622,11 @@ func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData []
|
|||||||
func newPostRequestV4WithContentLength(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string) (*http.Request, error) {
|
func newPostRequestV4WithContentLength(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string) (*http.Request, error) {
|
||||||
t := time.Now().UTC()
|
t := time.Now().UTC()
|
||||||
policy := buildGenericPolicy(t, accessKey, bucketName, objectName, true)
|
policy := buildGenericPolicy(t, accessKey, bucketName, objectName, true)
|
||||||
return newPostRequestV4Generic(endPoint, bucketName, objectName, objData, accessKey, secretKey, t, policy, false, false)
|
return newPostRequestV4Generic(endPoint, bucketName, objectName, objData, accessKey, secretKey, t, policy, nil, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPostRequestV4(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string) (*http.Request, error) {
|
func newPostRequestV4(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string) (*http.Request, error) {
|
||||||
t := time.Now().UTC()
|
t := time.Now().UTC()
|
||||||
policy := buildGenericPolicy(t, accessKey, bucketName, objectName, false)
|
policy := buildGenericPolicy(t, accessKey, bucketName, objectName, false)
|
||||||
return newPostRequestV4Generic(endPoint, bucketName, objectName, objData, accessKey, secretKey, t, policy, false, false)
|
return newPostRequestV4Generic(endPoint, bucketName, objectName, objData, accessKey, secretKey, t, policy, nil, false, false)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user