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:
Anis Elleuch 2016-12-20 18:32:17 +01:00 committed by Harshavardhana
parent faa6b1e925
commit d8e4d3c9c8
3 changed files with 137 additions and 21 deletions

View File

@ -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

View File

@ -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.

View File

@ -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)
}