mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -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()
|
||||
}
|
||||
|
||||
// writeSuccessResponse write success headers and response if any.
|
||||
// writeSuccessResponse writes success headers and response if any.
|
||||
func writeSuccessResponse(w http.ResponseWriter, response []byte) {
|
||||
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) {
|
||||
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) {
|
||||
apiError := getAPIError(errorCode)
|
||||
// set common headers
|
||||
|
@ -19,6 +19,7 @@ package cmd
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@ -447,21 +448,37 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"")
|
||||
w.Header().Set("Location", getObjectLocation(bucket, object))
|
||||
|
||||
// Decide what http response to send depending on success_action_status parameter
|
||||
switch 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)
|
||||
successRedirect := formValues[http.CanonicalHeaderKey("success_action_redirect")]
|
||||
successStatus := formValues[http.CanonicalHeaderKey("success_action_status")]
|
||||
|
||||
case "200":
|
||||
writeSuccessResponse(w, nil)
|
||||
default:
|
||||
if successStatus == "" && successRedirect == "" {
|
||||
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.
|
||||
|
@ -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.
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
// policy := buildGenericPolicy(curTime, testCase.accessKey, bucketName, testCase.objectName, false)
|
||||
testCase.policy = fmt.Sprintf(testCase.policy, testCase.dates...)
|
||||
|
||||
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 {
|
||||
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.
|
||||
func postPresignSignatureV4(policyBase64 string, t time.Time, secretAccessKey, location string) string {
|
||||
// Get signining key.
|
||||
@ -467,7 +554,8 @@ func buildGenericPolicy(t time.Time, accessKey, bucketName, objectName string, c
|
||||
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.
|
||||
credStr := getCredential(accessKey, serverConfig.GetRegion(), t)
|
||||
|
||||
@ -493,6 +581,11 @@ func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData []
|
||||
"Content-Encoding": "gzip",
|
||||
}
|
||||
|
||||
// Add form data
|
||||
for k, v := range addFormData {
|
||||
formData[k] = v
|
||||
}
|
||||
|
||||
// Create the multipart form.
|
||||
var buf bytes.Buffer
|
||||
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) {
|
||||
t := time.Now().UTC()
|
||||
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) {
|
||||
t := time.Now().UTC()
|
||||
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