From 5885ffc8aeb60a194e22b61c439e2b61ee55e988 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Fri, 30 Sep 2016 14:32:13 -0700 Subject: [PATCH] signature: Add legacy signature v2 support transparently. (#2811) Add new tests as well. --- cmd/auth-handler.go | 43 +- cmd/auth-handler_test.go | 53 +- cmd/bucket-handlers-listobjects.go | 14 + cmd/bucket-handlers.go | 29 + cmd/bucket-handlers_test.go | 10 +- cmd/bucket-notification-handlers_test.go | 20 +- cmd/bucket-policy-handlers.go | 56 +- cmd/bucket-policy-handlers_test.go | 144 +- cmd/control-lock-main.go | 2 +- cmd/lock-instrument.go | 21 +- cmd/lock-instrument_test.go | 12 +- cmd/object-handlers.go | 78 +- cmd/object-handlers_test.go | 14 +- cmd/server_test.go | 294 +-- cmd/server_v2_test.go | 2453 ++++++++++++++++++++++ cmd/signature-v2-utils.go | 56 + cmd/signature-v2.go | 303 +++ cmd/signature-v2_test.go | 159 ++ cmd/signature-v4-utils_test.go | 5 +- cmd/test-utils_test.go | 193 +- 20 files changed, 3619 insertions(+), 340 deletions(-) create mode 100644 cmd/server_v2_test.go create mode 100644 cmd/signature-v2-utils.go create mode 100644 cmd/signature-v2.go create mode 100644 cmd/signature-v2_test.go diff --git a/cmd/auth-handler.go b/cmd/auth-handler.go index 2df3c18fc..420b57db8 100644 --- a/cmd/auth-handler.go +++ b/cmd/auth-handler.go @@ -42,12 +42,24 @@ func isRequestSignatureV4(r *http.Request) bool { return strings.HasPrefix(r.Header.Get("Authorization"), signV4Algorithm) } +// Verify if request has AWS Signature Version '2'. +func isRequestSignatureV2(r *http.Request) bool { + return (!strings.HasPrefix(r.Header.Get("Authorization"), signV4Algorithm) && + strings.HasPrefix(r.Header.Get("Authorization"), signV2Algorithm)) +} + // Verify if request has AWS PreSign Version '4'. func isRequestPresignedSignatureV4(r *http.Request) bool { _, ok := r.URL.Query()["X-Amz-Credential"] return ok } +// Verify request has AWS PreSign Version '2'. +func isRequestPresignedSignatureV2(r *http.Request) bool { + _, ok := r.URL.Query()["AWSAccessKeyId"] + return ok +} + // Verify if request has AWS Post policy Signature Version '4'. func isRequestPostPolicySignatureV4(r *http.Request) bool { return strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") && r.Method == "POST" @@ -66,15 +78,21 @@ const ( authTypeUnknown authType = iota authTypeAnonymous authTypePresigned + authTypePresignedV2 authTypePostPolicy authTypeStreamingSigned authTypeSigned + authTypeSignedV2 authTypeJWT ) // Get request authentication type. func getRequestAuthType(r *http.Request) authType { - if isRequestSignStreamingV4(r) { + if isRequestSignatureV2(r) { + return authTypeSignedV2 + } else if isRequestPresignedSignatureV2(r) { + return authTypePresignedV2 + } else if isRequestSignStreamingV4(r) { return authTypeStreamingSigned } else if isRequestSignatureV4(r) { return authTypeSigned @@ -104,6 +122,14 @@ func sumMD5(data []byte) []byte { return hash.Sum(nil) } +// Verify if request has valid AWS Signature Version '2'. +func isReqAuthenticatedV2(r *http.Request) (s3Error APIErrorCode) { + if isRequestSignatureV2(r) { + return doesSignV2Match(r) + } + return doesPresignV2SignatureMatch(r) +} + // Verify if request has valid AWS Signature Version '4'. func isReqAuthenticated(r *http.Request, region string) (s3Error APIErrorCode) { if r == nil { @@ -111,6 +137,7 @@ func isReqAuthenticated(r *http.Request, region string) (s3Error APIErrorCode) { } payload, err := ioutil.ReadAll(r.Body) if err != nil { + errorIf(err, "Unable to read request body for signature verification") return ErrInternalError } // Verify Content-Md5, if payload is set. @@ -144,7 +171,6 @@ func isReqAuthenticated(r *http.Request, region string) (s3Error APIErrorCode) { // request headers and body are used to calculate the signature validating // the client signature present in request. func checkAuth(r *http.Request) APIErrorCode { - // Validates the request for both Presigned and Signed return checkAuthWithRegion(r, serverConfig.GetRegion()) } @@ -152,11 +178,14 @@ func checkAuth(r *http.Request) APIErrorCode { func checkAuthWithRegion(r *http.Request, region string) APIErrorCode { // Validates the request for both Presigned and Signed. aType := getRequestAuthType(r) - if aType != authTypePresigned && aType != authTypeSigned { - // For all unhandled auth types return error AccessDenied. - return ErrAccessDenied + switch aType { + case authTypeSignedV2, authTypePresignedV2: // Signature V2. + return isReqAuthenticatedV2(r) + case authTypeSigned, authTypePresigned: // Signature V4. + return isReqAuthenticated(r, region) } - return isReqAuthenticated(r, region) + // For all unhandled auth types return error AccessDenied. + return ErrAccessDenied } // authHandler - handles all the incoming authorization headers and validates them if possible. @@ -173,7 +202,9 @@ func setAuthHandler(h http.Handler) http.Handler { var supportedS3AuthTypes = map[authType]struct{}{ authTypeAnonymous: {}, authTypePresigned: {}, + authTypePresignedV2: {}, authTypeSigned: {}, + authTypeSignedV2: {}, authTypePostPolicy: {}, authTypeStreamingSigned: {}, } diff --git a/cmd/auth-handler_test.go b/cmd/auth-handler_test.go index 0cd694736..92e11bb1b 100644 --- a/cmd/auth-handler_test.go +++ b/cmd/auth-handler_test.go @@ -151,19 +151,29 @@ func TestS3SupportedAuthType(t *testing.T) { authT: authTypeStreamingSigned, pass: true, }, - // Test 6 - JWT is not supported s3 type. + // Test 6 - supported s3 type with signature v2. + { + authT: authTypeSignedV2, + pass: true, + }, + // Test 7 - supported s3 type with presign v2. + { + authT: authTypePresignedV2, + pass: true, + }, + // Test 8 - JWT is not supported s3 type. { authT: authTypeJWT, pass: false, }, - // Test 7 - unknown auth header is not supported s3 type. + // Test 9 - unknown auth header is not supported s3 type. { authT: authTypeUnknown, pass: false, }, - // Test 8 - some new auth type is not supported s3 type. + // Test 10 - some new auth type is not supported s3 type. { - authT: authType(7), + authT: authType(9), pass: false, }, } @@ -210,6 +220,39 @@ func TestIsRequestUnsignedPayload(t *testing.T) { } } +func TestIsRequestPresignedSignatureV2(t *testing.T) { + testCases := []struct { + inputQueryKey string + inputQueryValue string + expectedResult bool + }{ + // Test case - 1. + // Test case with query key "AWSAccessKeyId" set. + {"", "", false}, + // Test case - 2. + {"AWSAccessKeyId", "", true}, + // Test case - 3. + {"X-Amz-Content-Sha256", "", false}, + } + + for i, testCase := range testCases { + // creating an input HTTP request. + // Only the query parameters are relevant for this particular test. + inputReq, err := http.NewRequest("GET", "http://example.com", nil) + if err != nil { + t.Fatalf("Error initializing input HTTP request: %v", err) + } + q := inputReq.URL.Query() + q.Add(testCase.inputQueryKey, testCase.inputQueryValue) + inputReq.URL.RawQuery = q.Encode() + + actualResult := isRequestPresignedSignatureV2(inputReq) + if testCase.expectedResult != actualResult { + t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult) + } + } +} + // TestIsRequestPresignedSignatureV4 - Test validates the logic for presign signature verision v4 detection. func TestIsRequestPresignedSignatureV4(t *testing.T) { testCases := []struct { @@ -258,7 +301,7 @@ func mustNewRequest(method string, urlStr string, contentLength int64, body io.R func mustNewSignedRequest(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request { req := mustNewRequest(method, urlStr, contentLength, body, t) cred := serverConfig.GetCredential() - if err := signRequest(req, cred.AccessKeyID, cred.SecretAccessKey); err != nil { + if err := signRequestV4(req, cred.AccessKeyID, cred.SecretAccessKey); err != nil { t.Fatalf("Unable to inititalized new signed http request %s", err) } return req diff --git a/cmd/bucket-handlers-listobjects.go b/cmd/bucket-handlers-listobjects.go index 4135026a8..324f1fd87 100644 --- a/cmd/bucket-handlers-listobjects.go +++ b/cmd/bucket-handlers-listobjects.go @@ -81,6 +81,13 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http writeErrorResponse(w, r, s3Error, r.URL.Path) return } + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } case authTypeSigned, authTypePresigned: if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { errorIf(errSignatureMismatch, dumpRequest(r)) @@ -148,6 +155,13 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http writeErrorResponse(w, r, s3Error, r.URL.Path) return } + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } case authTypeSigned, authTypePresigned: if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { errorIf(errSignatureMismatch, dumpRequest(r)) diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index f679b1ec0..1ba39523e 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -93,6 +93,13 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r * writeErrorResponse(w, r, s3Error, r.URL.Path) return } + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } case authTypeSigned, authTypePresigned: if s3Error := isReqAuthenticated(r, "us-east-1"); s3Error != ErrNone { errorIf(errSignatureMismatch, dumpRequest(r)) @@ -149,6 +156,13 @@ func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter, writeErrorResponse(w, r, s3Error, r.URL.Path) return } + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } case authTypePresigned, authTypeSigned: if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { errorIf(errSignatureMismatch, dumpRequest(r)) @@ -198,6 +212,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R // List buckets does not support bucket policies, no need to enforce it. // Proceed to validate signature. + // Validates the request for both Presigned and Signed. if s3Error := checkAuthWithRegion(r, ""); s3Error != ErrNone { errorIf(errSignatureMismatch, dumpRequest(r)) writeErrorResponse(w, r, s3Error, r.URL.Path) @@ -243,6 +258,13 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, writeErrorResponse(w, r, s3Error, r.URL.Path) return } + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } case authTypePresigned, authTypeSigned: if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { errorIf(errSignatureMismatch, dumpRequest(r)) @@ -490,6 +512,13 @@ func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Re writeErrorResponse(w, r, s3Error, r.URL.Path) return } + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } case authTypePresigned, authTypeSigned: if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { errorIf(errSignatureMismatch, dumpRequest(r)) diff --git a/cmd/bucket-handlers_test.go b/cmd/bucket-handlers_test.go index 60b693ac8..f7616cdbb 100644 --- a/cmd/bucket-handlers_test.go +++ b/cmd/bucket-handlers_test.go @@ -94,7 +94,7 @@ func testGetBucketLocationHandler(obj ObjectLayer, instanceType string, t TestEr // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() // construct HTTP request for Get bucket location. - req, err := newTestSignedRequest("GET", getBucketLocationURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey) + req, err := newTestSignedRequestV4("GET", getBucketLocationURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey) if err != nil { t.Fatalf("Test %d: %s: Failed to create HTTP request for GetBucketLocationHandler: %v", i+1, instanceType, err) } @@ -187,7 +187,7 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType string, t TestErrHandle // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() // construct HTTP request for HEAD bucket. - req, err := newTestSignedRequest("HEAD", getHEADBucketURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey) + req, err := newTestSignedRequestV4("HEAD", getHEADBucketURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey) if err != nil { t.Fatalf("Test %d: %s: Failed to create HTTP request for HeadBucketHandler: %v", i+1, instanceType, err) } @@ -272,7 +272,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType string, t Tes // construct HTTP request for List multipart uploads endpoint. u := getListMultipartUploadsURLWithParams("", testCase.bucket, testCase.prefix, testCase.keyMarker, testCase.uploadIDMarker, testCase.delimiter, testCase.maxUploads) - req, gerr := newTestSignedRequest("GET", u, 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) + req, gerr := newTestSignedRequestV4("GET", u, 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) if gerr != nil { t.Fatalf("Test %d: %s: Failed to create HTTP request for ListMultipartUploadsHandler: %v", i+1, instanceType, gerr) } @@ -289,7 +289,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType string, t Tes // construct HTTP request for List multipart uploads endpoint. u := getListMultipartUploadsURLWithParams("", bucketName, "", "", "", "", "") - req, err := newTestSignedRequest("GET", u, 0, nil, "", "") // Generate an anonymous request. + req, err := newTestSignedRequestV4("GET", u, 0, nil, "", "") // Generate an anonymous request. if err != nil { t.Fatalf("Test %s: Failed to create HTTP request for ListMultipartUploadsHandler: %v", instanceType, err) } @@ -359,7 +359,7 @@ func testListBucketsHandler(obj ObjectLayer, instanceType string, t TestErrHandl for i, testCase := range testCases { // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() - req, lerr := newTestSignedRequest("GET", getListBucketURL(""), 0, nil, testCase.accessKey, testCase.secretKey) + req, lerr := newTestSignedRequestV4("GET", getListBucketURL(""), 0, nil, testCase.accessKey, testCase.secretKey) if lerr != nil { t.Fatalf("Test %d: %s: Failed to create HTTP request for ListBucketsHandler: %v", i+1, instanceType, lerr) } diff --git a/cmd/bucket-notification-handlers_test.go b/cmd/bucket-notification-handlers_test.go index e82dced58..ba082d333 100644 --- a/cmd/bucket-notification-handlers_test.go +++ b/cmd/bucket-notification-handlers_test.go @@ -221,7 +221,7 @@ func testGetBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te rec := httptest.NewRecorder() // Prepare notification config for one of the test cases. - req, err := newTestSignedRequest("PUT", getPutBucketNotificationURL("", randBucket), + req, err := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", randBucket), int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes), credentials.AccessKeyID, credentials.SecretAccessKey) if err != nil { @@ -251,7 +251,7 @@ func testGetBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te signatureMismatchCode := getAPIError(ErrContentSHA256Mismatch).Code for i, test := range testCases { testRec := httptest.NewRecorder() - testReq, tErr := newTestSignedRequest("GET", getGetBucketNotificationURL("", test.bucketName), + testReq, tErr := newTestSignedRequestV4("GET", getGetBucketNotificationURL("", test.bucketName), int64(0), nil, credentials.AccessKeyID, credentials.SecretAccessKey) if tErr != nil { t.Fatalf("Test %d: %s: Failed to create HTTP testRequest for GetBucketNotification: %v", @@ -308,7 +308,7 @@ func testGetBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te "PutBucketNotificationHandler", }) testRec := httptest.NewRecorder() - testReq, tErr := newTestSignedRequest("GET", getGetBucketNotificationURL("", randBucket), + testReq, tErr := newTestSignedRequestV4("GET", getGetBucketNotificationURL("", randBucket), int64(0), nil, credentials.AccessKeyID, credentials.SecretAccessKey) if tErr != nil { t.Fatalf("Test %d: %s: Failed to create HTTP testRequest for GetBucketNotification: %v", @@ -391,7 +391,7 @@ func testPutBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te } for i, test := range testCases { testRec := httptest.NewRecorder() - testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", test.bucketName), + testReq, tErr := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", test.bucketName), int64(len(test.expectedNotificationBytes)), bytes.NewReader(test.expectedNotificationBytes), credentials.AccessKeyID, credentials.SecretAccessKey) if tErr != nil { @@ -416,7 +416,7 @@ func testPutBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te switch test.kind { case CompareBytes: - testReq, tErr = newTestSignedRequest("GET", getGetBucketNotificationURL("", test.bucketName), + testReq, tErr = newTestSignedRequestV4("GET", getGetBucketNotificationURL("", test.bucketName), int64(0), nil, credentials.AccessKeyID, credentials.SecretAccessKey) if tErr != nil { t.Fatalf("Test %d: %s: Failed to create HTTP testRequest for GetBucketNotification: %v", @@ -464,7 +464,7 @@ func testPutBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te "PutBucketNotificationHandler", }) testRec := httptest.NewRecorder() - testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", randBucket), + testReq, tErr := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", randBucket), int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes), credentials.AccessKeyID, credentials.SecretAccessKey) if tErr != nil { @@ -526,7 +526,7 @@ func testListenBucketNotificationHandler(obj ObjectLayer, instanceType string, t instanceType, err) } testRec := httptest.NewRecorder() - testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", randBucket), + testReq, tErr := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", randBucket), int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes), credentials.AccessKeyID, credentials.SecretAccessKey) if tErr != nil { @@ -562,7 +562,7 @@ func testListenBucketNotificationHandler(obj ObjectLayer, instanceType string, t for i, test := range testCases { testRec = httptest.NewRecorder() - testReq, tErr = newTestSignedRequest("GET", + testReq, tErr = newTestSignedRequestV4("GET", getListenBucketNotificationURL("", test.bucketName, test.prefix, test.suffix, test.events), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) if tErr != nil { @@ -611,7 +611,7 @@ func testListenBucketNotificationHandler(obj ObjectLayer, instanceType string, t "ListenBucketNotificationHandler", }) testRec = httptest.NewRecorder() - testReq, tErr = newTestSignedRequest("GET", + testReq, tErr = newTestSignedRequestV4("GET", getListenBucketNotificationURL("", randBucket, "", "*.jpg", []string{"s3:ObjectCreated:*", "s3:ObjectRemoved:*"}), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) if tErr != nil { @@ -670,7 +670,7 @@ func testRemoveNotificationConfig(obj ObjectLayer, instanceType string, t TestEr } // Set sample bucket notification on randBucket. testRec := httptest.NewRecorder() - testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", randBucket), + testReq, tErr := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", randBucket), int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes), credentials.AccessKeyID, credentials.SecretAccessKey) if tErr != nil { diff --git a/cmd/bucket-policy-handlers.go b/cmd/bucket-policy-handlers.go index 4efd450ad..88a982a8c 100644 --- a/cmd/bucket-policy-handlers.go +++ b/cmd/bucket-policy-handlers.go @@ -132,19 +132,15 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht return } + // PutBucketPolicy does not support bucket policies, use checkAuth to validate signature. + if s3Error := checkAuth(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + vars := mux.Vars(r) bucket := vars["bucket"] - switch getRequestAuthType(r) { - default: - // For all unknown auth types return error. - writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path) - return - case authTypePresigned, authTypeSigned: - if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { - writeErrorResponse(w, r, s3Error, r.URL.Path) - return - } - } // If Content-Length is unknown or zero, deny the // request. PutBucketPolicy always needs a Content-Length if @@ -214,21 +210,16 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r return } + // DeleteBucketPolicy does not support bucket policies, use checkAuth to validate signature. + if s3Error := checkAuth(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + vars := mux.Vars(r) bucket := vars["bucket"] - switch getRequestAuthType(r) { - default: - // For all unknown auth types return error. - writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path) - return - case authTypePresigned, authTypeSigned: - if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { - writeErrorResponse(w, r, s3Error, r.URL.Path) - return - } - } - // Delete bucket access policy. if err := removeBucketPolicy(bucket, objAPI); err != nil { switch err.(type) { @@ -260,21 +251,16 @@ func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *ht return } + // GetBucketPolicy does not support bucket policies, use checkAuth to validate signature. + if s3Error := checkAuth(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + vars := mux.Vars(r) bucket := vars["bucket"] - switch getRequestAuthType(r) { - default: - // For all unknown auth types return error. - writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path) - return - case authTypePresigned, authTypeSigned: - if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { - writeErrorResponse(w, r, s3Error, r.URL.Path) - return - } - } - // Read bucket access policy. policy, err := readBucketPolicy(bucket, objAPI) if err != nil { diff --git a/cmd/bucket-policy-handlers_test.go b/cmd/bucket-policy-handlers_test.go index cc2b75c45..f1159e007 100644 --- a/cmd/bucket-policy-handlers_test.go +++ b/cmd/bucket-policy-handlers_test.go @@ -289,18 +289,32 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrH // obtain the put bucket policy request body. bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testCase.bucketName, testCase.bucketName) // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. - rec := httptest.NewRecorder() + recV4 := httptest.NewRecorder() // construct HTTP request for PUT bucket policy endpoint. - req, err := newTestSignedRequest("PUT", getPutPolicyURL("", testCase.bucketName), + reqV4, err := newTestSignedRequestV4("PUT", getPutPolicyURL("", testCase.bucketName), int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testCase.accessKey, testCase.secretKey) if err != nil { t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: %v", i+1, instanceType, err) } // 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) - if rec.Code != testCase.expectedRespStatus { - t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code) + apiRouter.ServeHTTP(recV4, reqV4) + if recV4.Code != testCase.expectedRespStatus { + t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV4.Code) + } + // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. + recV2 := httptest.NewRecorder() + // construct HTTP request for PUT bucket policy endpoint. + reqV2, err := newTestSignedRequestV2("PUT", getPutPolicyURL("", testCase.bucketName), + int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testCase.accessKey, testCase.secretKey) + if err != nil { + t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: %v", i+1, instanceType, err) + } + // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler. + // Call the ServeHTTP to execute the handler. + apiRouter.ServeHTTP(recV2, reqV2) + if recV2.Code != testCase.expectedRespStatus { + t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code) } } } @@ -357,18 +371,32 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrH // obtain the put bucket policy request body. bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testPolicy.bucketName, testPolicy.bucketName) // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. - rec := httptest.NewRecorder() + recV4 := httptest.NewRecorder() // construct HTTP request for PUT bucket policy endpoint. - req, err := newTestSignedRequest("PUT", getPutPolicyURL("", testPolicy.bucketName), + reqV4, err := newTestSignedRequestV4("PUT", getPutPolicyURL("", testPolicy.bucketName), int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey) if err != nil { t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: %v", i+1, err) } // 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) - if rec.Code != testPolicy.expectedRespStatus { - t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, rec.Code) + apiRouter.ServeHTTP(recV4, reqV4) + if recV4.Code != testPolicy.expectedRespStatus { + t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, recV4.Code) + } + // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. + recV2 := httptest.NewRecorder() + // construct HTTP request for PUT bucket policy endpoint. + reqV2, err := newTestSignedRequestV2("PUT", getPutPolicyURL("", testPolicy.bucketName), + int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey) + if err != nil { + t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: %v", i+1, err) + } + // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler. + // Call the ServeHTTP to execute the handler. + apiRouter.ServeHTTP(recV2, reqV2) + if recV2.Code != testPolicy.expectedRespStatus { + t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, recV2.Code) } } @@ -388,9 +416,9 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrH // expected bucket policy json string. expectedBucketPolicyStr := fmt.Sprintf(testCase.expectedBucketPolicy, testCase.bucketName, testCase.bucketName) // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. - rec := httptest.NewRecorder() + recV4 := httptest.NewRecorder() // construct HTTP request for PUT bucket policy endpoint. - req, err := newTestSignedRequest("GET", getGetPolicyURL("", testCase.bucketName), + reqV4, err := newTestSignedRequestV4("GET", getGetPolicyURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey) if err != nil { @@ -398,13 +426,37 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrH } // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. // Call the ServeHTTP to execute the handler, GetBucketPolicyHandler handles the request. - apiRouter.ServeHTTP(rec, req) + apiRouter.ServeHTTP(recV4, reqV4) // Assert the response code with the expected status. - if rec.Code != testCase.expectedRespStatus { - t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code) + if recV4.Code != testCase.expectedRespStatus { + t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV4.Code) } // read the response body. - bucketPolicyReadBuf, err := ioutil.ReadAll(rec.Body) + bucketPolicyReadBuf, err := ioutil.ReadAll(recV4.Body) + if err != nil { + t.Fatalf("Test %d: %s: Failed parsing response body: %v", i+1, instanceType, err) + } + // Verify whether the bucket policy fetched is same as the one inserted. + if expectedBucketPolicyStr != string(bucketPolicyReadBuf) { + t.Errorf("Test %d: %s: Bucket policy differs from expected value.", i+1, instanceType) + } + // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. + recV2 := httptest.NewRecorder() + // construct HTTP request for PUT bucket policy endpoint. + reqV2, err := newTestSignedRequestV2("GET", getGetPolicyURL("", testCase.bucketName), + 0, nil, testCase.accessKey, testCase.secretKey) + if err != nil { + t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: %v", i+1, err) + } + // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. + // Call the ServeHTTP to execute the handler, GetBucketPolicyHandler handles the request. + apiRouter.ServeHTTP(recV2, reqV2) + // Assert the response code with the expected status. + if recV2.Code != testCase.expectedRespStatus { + t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV2.Code) + } + // read the response body. + bucketPolicyReadBuf, err = ioutil.ReadAll(recV2.Body) if err != nil { t.Fatalf("Test %d: %s: Failed parsing response body: %v", i+1, instanceType, err) } @@ -502,20 +554,21 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE // obtain the put bucket policy request body. bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testPolicy.bucketName, testPolicy.bucketName) // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. - rec := httptest.NewRecorder() + recV4 := httptest.NewRecorder() // construct HTTP request for PUT bucket policy endpoint. - req, err := newTestSignedRequest("PUT", getPutPolicyURL("", testPolicy.bucketName), + reqV4, err := newTestSignedRequestV4("PUT", getPutPolicyURL("", testPolicy.bucketName), int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey) if err != nil { t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: %v", i+1, err) } // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. // Call the ServeHTTP to execute the handler. - apiRouter.ServeHTTP(rec, req) - if rec.Code != testPolicy.expectedRespStatus { - t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, rec.Code) + apiRouter.ServeHTTP(recV4, reqV4) + if recV4.Code != testPolicy.expectedRespStatus { + t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, recV4.Code) } } + // testcases with input and expected output for DeleteBucketPolicyHandler. testCases := []struct { bucketName string @@ -529,21 +582,58 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE // Iterating over the cases and deleting the bucket policy and then asserting response. for i, testCase := range testCases { // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. - rec := httptest.NewRecorder() + recV4 := httptest.NewRecorder() // construct HTTP request for Delete bucket policy endpoint. - req, err := newTestSignedRequest("DELETE", getDeletePolicyURL("", testCase.bucketName), + reqV4, err := newTestSignedRequestV4("DELETE", getDeletePolicyURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey) - if err != nil { t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: %v", i+1, err) } // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. // Call the ServeHTTP to execute the handler, DeleteBucketPolicyHandler handles the request. - apiRouter.ServeHTTP(rec, req) + apiRouter.ServeHTTP(recV4, reqV4) // Assert the response code with the expected status. - if rec.Code != testCase.expectedRespStatus { - t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code) + if recV4.Code != testCase.expectedRespStatus { + t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV4.Code) } + } + // Iterating over the cases and writing the bucket policy. + // its required to write the policies first before running tests on GetBucketPolicy. + for i, testPolicy := range putTestPolicies { + // obtain the put bucket policy request body. + bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testPolicy.bucketName, testPolicy.bucketName) + // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. + recV2 := httptest.NewRecorder() + // construct HTTP request for PUT bucket policy endpoint. + reqV2, err := newTestSignedRequestV2("PUT", getPutPolicyURL("", testPolicy.bucketName), + int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey) + if err != nil { + t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: %v", i+1, err) + } + // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. + // Call the ServeHTTP to execute the handler. + apiRouter.ServeHTTP(recV2, reqV2) + if recV2.Code != testPolicy.expectedRespStatus { + t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, recV2.Code) + } + } + + for i, testCase := range testCases { + // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. + recV2 := httptest.NewRecorder() + // construct HTTP request for Delete bucket policy endpoint. + reqV2, err := newTestSignedRequestV2("DELETE", getDeletePolicyURL("", testCase.bucketName), + 0, nil, testCase.accessKey, testCase.secretKey) + if err != nil { + t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: %v", i+1, err) + } + // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. + // Call the ServeHTTP to execute the handler, DeleteBucketPolicyHandler handles the request. + apiRouter.ServeHTTP(recV2, reqV2) + // Assert the response code with the expected status. + if recV2.Code != testCase.expectedRespStatus { + t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV2.Code) + } } } diff --git a/cmd/control-lock-main.go b/cmd/control-lock-main.go index 2c8fba060..a5e0982ed 100644 --- a/cmd/control-lock-main.go +++ b/cmd/control-lock-main.go @@ -61,7 +61,7 @@ func generateSystemLockResponse() (SystemLockState, error) { defer nsMutex.lockMapMutex.Unlock() if nsMutex.debugLockMap == nil { - return SystemLockState{}, LockInfoNil{} + return SystemLockState{}, errLockNotInitialized } lockState := SystemLockState{} diff --git a/cmd/lock-instrument.go b/cmd/lock-instrument.go index 90f689409..94199f2db 100644 --- a/cmd/lock-instrument.go +++ b/cmd/lock-instrument.go @@ -17,6 +17,7 @@ package cmd import ( + "errors" "fmt" "time" ) @@ -58,14 +59,6 @@ func newDebugLockInfoPerVolumePath() *debugLockInfoPerVolumePath { } } -// LockInfoNil - Returned if the lock info map is not initialized. -type LockInfoNil struct { -} - -func (l LockInfoNil) Error() string { - return fmt.Sprintf("Debug Lock Map not initialized:\n1. Enable Lock Debugging using right ENV settings \n2. Make sure initNSLock() is called.") -} - // LockInfoOriginNotFound - While changing the state of the lock info its important that the entry for // lock at a given origin exists, if not `LockInfoOriginNotFound` is returned. type LockInfoOriginNotFound struct { @@ -114,13 +107,15 @@ func (l LockInfoStateNotBlocked) Error() string { return fmt.Sprintf("Lock state should be \"Blocked\" for %s, %s, %s.", l.volume, l.path, l.operationID) } +var errLockNotInitialized = errors.New("Debug Lock Map not initialized:\n1. Enable Lock Debugging using right ENV settings \n2. Make sure initNSLock() is called.") + // change the state of the lock from Blocked to Running. func (n *nsLockMap) statusBlockedToRunning(param nsParam, lockOrigin, operationID string, readLock bool) error { // This operation is not executed under the scope nsLockMap.mutex.Lock(), lock has to be explicitly held here. n.lockMapMutex.Lock() defer n.lockMapMutex.Unlock() if n.debugLockMap == nil { - return LockInfoNil{} + return errLockNotInitialized } // new state info to be set for the lock. newLockInfo := debugLockInfo{ @@ -140,7 +135,7 @@ func (n *nsLockMap) statusBlockedToRunning(param nsParam, lockOrigin, operationI if debugLockMap, ok := n.debugLockMap[param]; ok { // ``*debugLockInfoPerVolumePath` entry containing lock info for `param ` is `nil`. if debugLockMap == nil { - return LockInfoNil{} + return errLockNotInitialized } } else { // The lock state info foe given pair should already exist. @@ -186,7 +181,7 @@ func (n *nsLockMap) statusBlockedToRunning(param nsParam, lockOrigin, operationI // change the state of the lock from Ready to Blocked. func (n *nsLockMap) statusNoneToBlocked(param nsParam, lockOrigin, operationID string, readLock bool) error { if n.debugLockMap == nil { - return LockInfoNil{} + return errLockNotInitialized } newLockInfo := debugLockInfo{ @@ -230,7 +225,7 @@ func (n *nsLockMap) statusNoneToBlocked(param nsParam, lockOrigin, operationID s // deleteLockInfoEntry - Deletes the lock state information for given pair. Called when nsLk.ref count is 0. func (n *nsLockMap) deleteLockInfoEntryForVolumePath(param nsParam) error { if n.debugLockMap == nil { - return LockInfoNil{} + return errLockNotInitialized } // delete the lock info for the given operation. if _, found := n.debugLockMap[param]; found { @@ -246,7 +241,7 @@ func (n *nsLockMap) deleteLockInfoEntryForVolumePath(param nsParam) error { // called when the nsLk ref count for the given pair is not 0. func (n *nsLockMap) deleteLockInfoEntryForOps(param nsParam, operationID string) error { if n.debugLockMap == nil { - return LockInfoNil{} + return errLockNotInitialized } // delete the lock info for the given operation. if infoMap, found := n.debugLockMap[param]; found { diff --git a/cmd/lock-instrument_test.go b/cmd/lock-instrument_test.go index 1fdb37e58..35d18d5ee 100644 --- a/cmd/lock-instrument_test.go +++ b/cmd/lock-instrument_test.go @@ -327,7 +327,7 @@ func TestNsLockMapStatusBlockedToRunning(t *testing.T) { actualErr := nsMutex.statusBlockedToRunning(param, testCases[0].lockOrigin, testCases[0].opsID, testCases[0].readLock) - expectedNilErr := LockInfoNil{} + expectedNilErr := errLockNotInitialized if actualErr != expectedNilErr { t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr) } @@ -338,12 +338,12 @@ func TestNsLockMapStatusBlockedToRunning(t *testing.T) { lockMap: make(map[nsParam]*nsLock), } // Entry for pair is set to nil. - // Should fail with `LockInfoNil{}`. + // Should fail with `errLockNotInitialized`. nsMutex.debugLockMap[param] = nil actualErr = nsMutex.statusBlockedToRunning(param, testCases[0].lockOrigin, testCases[0].opsID, testCases[0].readLock) - expectedNilErr = LockInfoNil{} + expectedNilErr = errLockNotInitialized if actualErr != expectedNilErr { t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr) } @@ -524,7 +524,7 @@ func TestNsLockMapStatusNoneToBlocked(t *testing.T) { actualErr := nsMutex.statusBlockedToRunning(param, testCases[0].lockOrigin, testCases[0].opsID, testCases[0].readLock) - expectedNilErr := LockInfoNil{} + expectedNilErr := errLockNotInitialized if actualErr != expectedNilErr { t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr) } @@ -569,7 +569,7 @@ func TestNsLockMapDeleteLockInfoEntryForOps(t *testing.T) { actualErr := nsMutex.deleteLockInfoEntryForOps(param, testCases[0].opsID) - expectedNilErr := LockInfoNil{} + expectedNilErr := errLockNotInitialized if actualErr != expectedNilErr { t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr) } @@ -667,7 +667,7 @@ func TestNsLockMapDeleteLockInfoEntryForVolumePath(t *testing.T) { actualErr := nsMutex.deleteLockInfoEntryForVolumePath(param) - expectedNilErr := LockInfoNil{} + expectedNilErr := errLockNotInitialized if actualErr != expectedNilErr { t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr) } diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index 41a695373..50db9e64a 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -110,6 +110,13 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req writeErrorResponse(w, r, s3Error, r.URL.Path) return } + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } case authTypePresigned, authTypeSigned: if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { errorIf(errSignatureMismatch, dumpRequest(r)) @@ -222,6 +229,13 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re writeErrorResponse(w, r, s3Error, r.URL.Path) return } + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } case authTypePresigned, authTypeSigned: if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { errorIf(errSignatureMismatch, dumpRequest(r)) @@ -279,6 +293,13 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re writeErrorResponse(w, r, s3Error, r.URL.Path) return } + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } case authTypePresigned, authTypeSigned: if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { errorIf(errSignatureMismatch, dumpRequest(r)) @@ -399,12 +420,12 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req return } - // If the matching failed, it means that the X-Amz-Copy-Source was - // wrong, fail right here. + // X-Amz-Copy-Source shouldn't be set for this call. if _, ok := r.Header["X-Amz-Copy-Source"]; ok { writeErrorResponse(w, r, ErrInvalidCopySource, r.URL.Path) return } + vars := mux.Vars(r) bucket := vars["bucket"] object := vars["object"] @@ -416,6 +437,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req writeErrorResponse(w, r, ErrInvalidDigest, r.URL.Path) return } + /// if Content-Length is unknown/missing, deny the request size := r.ContentLength rAuthType := getRequestAuthType(r) @@ -432,6 +454,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req writeErrorResponse(w, r, ErrMissingContentLength, r.URL.Path) return } + /// maximum Upload size for objects in a single operation if isMaxObjectSize(size) { writeErrorResponse(w, r, ErrEntityTooLarge, r.URL.Path) @@ -466,6 +489,14 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req return } objInfo, err = objectAPI.PutObject(bucket, object, size, reader, metadata) + case authTypeSignedV2, authTypePresignedV2: + s3Error := isReqAuthenticatedV2(r) + if s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + objInfo, err = objectAPI.PutObject(bucket, object, size, r.Body, metadata) case authTypePresigned, authTypeSigned: // Initialize signature verifier. reader := newSignVerify(r) @@ -517,6 +548,13 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r writeErrorResponse(w, r, s3Error, r.URL.Path) return } + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } case authTypePresigned, authTypeSigned: if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { errorIf(errSignatureMismatch, dumpRequest(r)) @@ -626,6 +664,14 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http return } partMD5, err = objectAPI.PutObjectPart(bucket, object, uploadID, partID, size, reader, incomingMD5) + case authTypeSignedV2, authTypePresignedV2: + s3Error := isReqAuthenticatedV2(r) + if s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } + partMD5, err = objectAPI.PutObjectPart(bucket, object, uploadID, partID, size, r.Body, incomingMD5) case authTypePresigned, authTypeSigned: // Initialize signature verifier. reader := newSignVerify(r) @@ -666,6 +712,13 @@ func (api objectAPIHandlers) AbortMultipartUploadHandler(w http.ResponseWriter, writeErrorResponse(w, r, s3Error, r.URL.Path) return } + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } case authTypePresigned, authTypeSigned: if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { errorIf(errSignatureMismatch, dumpRequest(r)) @@ -706,6 +759,13 @@ func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *ht writeErrorResponse(w, r, s3Error, r.URL.Path) return } + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } case authTypePresigned, authTypeSigned: if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { errorIf(errSignatureMismatch, dumpRequest(r)) @@ -764,6 +824,13 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite writeErrorResponse(w, r, s3Error, r.URL.Path) return } + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } case authTypePresigned, authTypeSigned: if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { errorIf(errSignatureMismatch, dumpRequest(r)) @@ -873,6 +940,13 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http. writeErrorResponse(w, r, s3Error, r.URL.Path) return } + case authTypePresignedV2, authTypeSignedV2: + // Signature V2 validation. + if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone { + errorIf(errSignatureMismatch, dumpRequest(r)) + writeErrorResponse(w, r, s3Error, r.URL.Path) + return + } case authTypeSigned, authTypePresigned: if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone { errorIf(errSignatureMismatch, dumpRequest(r)) diff --git a/cmd/object-handlers_test.go b/cmd/object-handlers_test.go index 59885df7b..8f47d880a 100644 --- a/cmd/object-handlers_test.go +++ b/cmd/object-handlers_test.go @@ -128,7 +128,7 @@ func testAPIGetOjectHandler(obj ObjectLayer, instanceType, bucketName string, ap // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() // construct HTTP request for Get Object end point. - req, err := newTestSignedRequest("GET", getGetObjectURL("", testCase.bucketName, testCase.objectName), + req, err := newTestSignedRequestV4("GET", getGetObjectURL("", testCase.bucketName, testCase.objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) if err != nil { @@ -342,7 +342,7 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() // construct HTTP request for Get Object end point. - req, err := newTestSignedRequest("PUT", getPutObjectURL("", testCase.bucketName, testCase.objectName), + req, err := newTestSignedRequestV4("PUT", getPutObjectURL("", testCase.bucketName, testCase.objectName), int64(testCase.dataLen), bytes.NewReader(testCase.data), credentials.AccessKeyID, credentials.SecretAccessKey) if err != nil { t.Fatalf("Test %d: Failed to create HTTP request for Put Object: %v", i+1, err) @@ -484,7 +484,7 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string, // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() // construct HTTP request for copy object. - req, err := newTestSignedRequest("PUT", getCopyObjectURL("", testCase.bucketName, testCase.newObjectName), + req, err := newTestSignedRequestV4("PUT", getCopyObjectURL("", testCase.bucketName, testCase.newObjectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) if err != nil { @@ -529,7 +529,7 @@ func testAPINewMultipartHandler(obj ObjectLayer, instanceType, bucketName string objectName := "test-object-new-multipart" rec := httptest.NewRecorder() // construct HTTP request for copy object. - req, err := newTestSignedRequest("POST", getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) + req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) if err != nil { t.Fatalf("Failed to create HTTP request for copy Object: %v", err) @@ -581,7 +581,7 @@ func testAPINewMultipartHandlerParallel(obj ObjectLayer, instanceType, bucketNam defer wg.Done() rec := httptest.NewRecorder() // construct HTTP request for copy object. - req, err := newTestSignedRequest("POST", getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) + req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) if err != nil { t.Fatalf("Failed to create HTTP request for copy Object: %v", err) @@ -828,7 +828,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s t.Fatalf("Error XML encoding of parts: %s.", err) } // Indicating that all parts are uploaded and initiating completeMultipartUpload. - req, err = newTestSignedRequest("POST", getCompleteMultipartUploadURL("", bucketName, objectName, testCase.uploadID), + req, err = newTestSignedRequestV4("POST", getCompleteMultipartUploadURL("", bucketName, objectName, testCase.uploadID), int64(len(completeBytes)), bytes.NewReader(completeBytes), credentials.AccessKeyID, credentials.SecretAccessKey) if err != nil { t.Fatalf("Failed to create HTTP request for copy Object: %v", err) @@ -930,7 +930,7 @@ func testAPIDeleteOjectHandler(obj ObjectLayer, instanceType, bucketName string, // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() // construct HTTP request for Get Object end point. - req, err := newTestSignedRequest("DELETE", getDeleteObjectURL("", testCase.bucketName, testCase.objectName), + req, err := newTestSignedRequestV4("DELETE", getDeleteObjectURL("", testCase.bucketName, testCase.objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) if err != nil { diff --git a/cmd/server_test.go b/cmd/server_test.go index 185c7dfa8..2d5d6da82 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -81,7 +81,7 @@ func (s *TestSuiteCommon) TestBucketSQSNotification(c *C) { // generate a random bucket Name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -93,7 +93,7 @@ func (s *TestSuiteCommon) TestBucketSQSNotification(c *C) { // assert the http response status code. c.Assert(response.StatusCode, Equals, http.StatusOK) - request, err = newTestSignedRequest("PUT", getPutNotificationURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("PUT", getPutNotificationURL(s.endPoint, bucketName), int64(len(bucketNotificationBuf)), bytes.NewReader([]byte(bucketNotificationBuf)), s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -113,7 +113,7 @@ func (s *TestSuiteCommon) TestBucketSNSNotification(c *C) { // generate a random bucket Name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -124,7 +124,7 @@ func (s *TestSuiteCommon) TestBucketSNSNotification(c *C) { // assert the http response status code. c.Assert(response.StatusCode, Equals, http.StatusOK) - request, err = newTestSignedRequest("PUT", getPutNotificationURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("PUT", getPutNotificationURL(s.endPoint, bucketName), int64(len(bucketNotificationBuf)), bytes.NewReader([]byte(bucketNotificationBuf)), s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -136,7 +136,7 @@ func (s *TestSuiteCommon) TestBucketSNSNotification(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // Fetch the uploaded policy. - request, err = newTestSignedRequest("GET", getGetNotificationURL(s.endPoint, bucketName), 0, nil, + request, err = newTestSignedRequestV4("GET", getGetNotificationURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -152,7 +152,7 @@ func (s *TestSuiteCommon) TestBucketSNSNotification(c *C) { invalidBucketNotificationBuf := `s3:ObjectCreated:Putinvalidimages/1arn:minio:sns:us-east-1:444455556666:minio` - request, err = newTestSignedRequest("PUT", getPutNotificationURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("PUT", getPutNotificationURL(s.endPoint, bucketName), int64(len(invalidBucketNotificationBuf)), bytes.NewReader([]byte(invalidBucketNotificationBuf)), s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -165,7 +165,7 @@ func (s *TestSuiteCommon) TestBucketSNSNotification(c *C) { invalidBucketNotificationBuf = `s3:ObjectCreated:Putinvalidimages/1arn:minio:sns:us-east-1:1:listen` - request, err = newTestSignedRequest("PUT", getPutNotificationURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("PUT", getPutNotificationURL(s.endPoint, bucketName), int64(len(invalidBucketNotificationBuf)), bytes.NewReader([]byte(invalidBucketNotificationBuf)), s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -178,7 +178,7 @@ func (s *TestSuiteCommon) TestBucketSNSNotification(c *C) { invalidBucketNotificationBuf = `s3:ObjectCreated:Putprefix|||1arn:minio:sns:us-east-1:1:listen` - request, err = newTestSignedRequest("PUT", getPutNotificationURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("PUT", getPutNotificationURL(s.endPoint, bucketName), int64(len(invalidBucketNotificationBuf)), bytes.NewReader([]byte(invalidBucketNotificationBuf)), s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -190,7 +190,7 @@ func (s *TestSuiteCommon) TestBucketSNSNotification(c *C) { verifyError(c, response, "InvalidArgument", "Size of filter rule value cannot exceed 1024 bytes in UTF-8 representation", http.StatusBadRequest) invalidBucketNotificationBuf = `s3:ObjectCreated:Putprefiximages/1arn:minio:sns:us-west-1:444455556666:listen` - request, err = newTestSignedRequest("PUT", getPutNotificationURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("PUT", getPutNotificationURL(s.endPoint, bucketName), int64(len(invalidBucketNotificationBuf)), bytes.NewReader([]byte(invalidBucketNotificationBuf)), s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -202,7 +202,7 @@ func (s *TestSuiteCommon) TestBucketSNSNotification(c *C) { verifyError(c, response, "InvalidArgument", "A specified destination is in a different region than the bucket. You must use a destination that resides in the same region as the bucket.", http.StatusBadRequest) invalidBucketNotificationBuf = `s3:ObjectCreated:Invalidprefiximages/1arn:minio:sns:us-east-1:444455556666:listen` - request, err = newTestSignedRequest("PUT", getPutNotificationURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("PUT", getPutNotificationURL(s.endPoint, bucketName), int64(len(invalidBucketNotificationBuf)), bytes.NewReader([]byte(invalidBucketNotificationBuf)), s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -213,7 +213,7 @@ func (s *TestSuiteCommon) TestBucketSNSNotification(c *C) { verifyError(c, response, "InvalidArgument", "A specified event is not supported for notifications.", http.StatusBadRequest) bucketNotificationDuplicates := `s3:ObjectCreated:Putprefiximages/1arn:minio:sns:us-east-1:444455556666:listens3:ObjectCreated:Putprefiximages/1arn:minio:sns:us-east-1:444455556666:listen` - request, err = newTestSignedRequest("PUT", getPutNotificationURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("PUT", getPutNotificationURL(s.endPoint, bucketName), int64(len(bucketNotificationDuplicates)), bytes.NewReader([]byte(bucketNotificationDuplicates)), s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -235,7 +235,7 @@ func (s *TestSuiteCommon) TestBucketPolicy(c *C) { // create the policy statement string with the randomly generated bucket name. bucketPolicyStr := fmt.Sprintf(bucketPolicyBuf, bucketName, bucketName) // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -247,7 +247,7 @@ func (s *TestSuiteCommon) TestBucketPolicy(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) /// Put a new bucket policy. - request, err = newTestSignedRequest("PUT", getPutPolicyURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("PUT", getPutPolicyURL(s.endPoint, bucketName), int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -258,7 +258,7 @@ func (s *TestSuiteCommon) TestBucketPolicy(c *C) { c.Assert(response.StatusCode, Equals, http.StatusNoContent) // Fetch the uploaded policy. - request, err = newTestSignedRequest("GET", getGetPolicyURL(s.endPoint, bucketName), 0, nil, + request, err = newTestSignedRequestV4("GET", getGetPolicyURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -273,7 +273,7 @@ func (s *TestSuiteCommon) TestBucketPolicy(c *C) { c.Assert(bytes.Equal([]byte(bucketPolicyStr), bucketPolicyReadBuf), Equals, true) // Delete policy. - request, err = newTestSignedRequest("DELETE", getDeletePolicyURL(s.endPoint, bucketName), 0, nil, + request, err = newTestSignedRequestV4("DELETE", getDeletePolicyURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -283,7 +283,7 @@ func (s *TestSuiteCommon) TestBucketPolicy(c *C) { c.Assert(response.StatusCode, Equals, http.StatusNoContent) // Verify if the policy was indeed deleted. - request, err = newTestSignedRequest("GET", getGetPolicyURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("GET", getGetPolicyURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -298,7 +298,7 @@ func (s *TestSuiteCommon) TestDeleteBucket(c *C) { bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -309,7 +309,7 @@ func (s *TestSuiteCommon) TestDeleteBucket(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // construct request to delete the bucket. - request, err = newTestSignedRequest("DELETE", getDeleteBucketURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("DELETE", getDeleteBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -326,7 +326,7 @@ func (s *TestSuiteCommon) TestDeleteBucketNotEmpty(c *C) { bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -340,7 +340,7 @@ func (s *TestSuiteCommon) TestDeleteBucketNotEmpty(c *C) { // generate http request for an object upload. // "test-object" is the object name. objectName := "test-object" - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -354,7 +354,7 @@ func (s *TestSuiteCommon) TestDeleteBucketNotEmpty(c *C) { // constructing http request to delete the bucket. // making an attempt to delete an non-empty bucket. // expected to fail. - request, err = newTestSignedRequest("DELETE", getDeleteBucketURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("DELETE", getDeleteBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -370,7 +370,7 @@ func (s *TestSuiteCommon) TestDeleteMultipleObjects(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -389,7 +389,7 @@ func (s *TestSuiteCommon) TestDeleteMultipleObjects(c *C) { // Obtain http request to upload object. // object Name contains a prefix. objName := fmt.Sprintf("%d/%s", i, objectName) - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -409,7 +409,7 @@ func (s *TestSuiteCommon) TestDeleteMultipleObjects(c *C) { c.Assert(err, IsNil) // Delete list of objects. - request, err = newTestSignedRequest("POST", getMultiDeleteObjectURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("POST", getMultiDeleteObjectURL(s.endPoint, bucketName), int64(len(deleteReqBytes)), bytes.NewReader(deleteReqBytes), s.accessKey, s.secretKey) c.Assert(err, IsNil) client = http.Client{} @@ -430,7 +430,7 @@ func (s *TestSuiteCommon) TestDeleteMultipleObjects(c *C) { // Attempt second time results should be same, NoSuchKey for objects not found // shouldn't be set. - request, err = newTestSignedRequest("POST", getMultiDeleteObjectURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("POST", getMultiDeleteObjectURL(s.endPoint, bucketName), int64(len(deleteReqBytes)), bytes.NewReader(deleteReqBytes), s.accessKey, s.secretKey) c.Assert(err, IsNil) client = http.Client{} @@ -454,7 +454,7 @@ func (s *TestSuiteCommon) TestDeleteObject(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -468,7 +468,7 @@ func (s *TestSuiteCommon) TestDeleteObject(c *C) { objectName := "prefix/myobject" // obtain http request to upload object. // object Name contains a prefix. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -481,7 +481,7 @@ func (s *TestSuiteCommon) TestDeleteObject(c *C) { // object name was "prefix/myobject", an attempt to delelte "prefix" // Should not delete "prefix/myobject" - request, err = newTestSignedRequest("DELETE", getDeleteObjectURL(s.endPoint, bucketName, "prefix"), + request, err = newTestSignedRequestV4("DELETE", getDeleteObjectURL(s.endPoint, bucketName, "prefix"), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) client = http.Client{} @@ -491,7 +491,7 @@ func (s *TestSuiteCommon) TestDeleteObject(c *C) { // create http request to HEAD on the object. // this helps to validate the existence of the bucket. - request, err = newTestSignedRequest("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -502,7 +502,7 @@ func (s *TestSuiteCommon) TestDeleteObject(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // create HTTP request to delete the object. - request, err = newTestSignedRequest("DELETE", getDeleteObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("DELETE", getDeleteObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) client = http.Client{} @@ -513,7 +513,7 @@ func (s *TestSuiteCommon) TestDeleteObject(c *C) { c.Assert(response.StatusCode, Equals, http.StatusNoContent) // Delete of non-existent data should return success. - request, err = newTestSignedRequest("DELETE", getDeleteObjectURL(s.endPoint, bucketName, "prefix/myobject1"), + request, err = newTestSignedRequestV4("DELETE", getDeleteObjectURL(s.endPoint, bucketName, "prefix/myobject1"), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) client = http.Client{} @@ -530,7 +530,7 @@ func (s *TestSuiteCommon) TestNonExistentBucket(c *C) { bucketName := getRandomBucketName() // create request to HEAD on the bucket. // HEAD on an bucket helps validate the existence of the bucket. - request, err := newTestSignedRequest("HEAD", getHEADBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("HEAD", getHEADBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -547,7 +547,7 @@ func (s *TestSuiteCommon) TestEmptyObject(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -560,7 +560,7 @@ func (s *TestSuiteCommon) TestEmptyObject(c *C) { objectName := "test-object" // construct http request for uploading the object. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -572,7 +572,7 @@ func (s *TestSuiteCommon) TestEmptyObject(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // make HTTP request to fetch the object. - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -595,7 +595,7 @@ func (s *TestSuiteCommon) TestBucket(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -604,7 +604,7 @@ func (s *TestSuiteCommon) TestBucket(c *C) { c.Assert(err, IsNil) c.Assert(response.StatusCode, Equals, http.StatusOK) - request, err = newTestSignedRequest("HEAD", getMakeBucketURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("HEAD", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -620,7 +620,7 @@ func (s *TestSuiteCommon) TestObjectGetAnonymous(c *C) { bucketName := getRandomBucketName() buffer := bytes.NewReader([]byte("hello world")) // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -633,7 +633,7 @@ func (s *TestSuiteCommon) TestObjectGetAnonymous(c *C) { objectName := "testObject" // create HTTP request to upload the object. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buffer.Len()), buffer, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -663,7 +663,7 @@ func (s *TestSuiteCommon) TestObjectGet(c *C) { bucketName := getRandomBucketName() buffer := bytes.NewReader([]byte("hello world")) // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -676,7 +676,7 @@ func (s *TestSuiteCommon) TestObjectGet(c *C) { objectName := "testObject" // create HTTP request to upload the object. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buffer.Len()), buffer, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -694,7 +694,7 @@ func (s *TestSuiteCommon) TestObjectGet(c *C) { defer wg.Done() // HTTP request to create the bucket. // create HTTP request to fetch the object. - getRequest, err := newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + getRequest, err := newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -722,7 +722,7 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -735,7 +735,7 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *C) { // constructing HTTP request to fetch a non-existent object. // expected to fail, error response asserted for expected error values later. objectName := "testObject" - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -750,7 +750,7 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *C) { // content for the object to be uploaded. buffer1 := bytes.NewReader([]byte("hello one")) // create HTTP request for the object upload. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -762,7 +762,7 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // create HTTP request to fetch the object which was uploaded above. - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -782,7 +782,7 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *C) { // data for new object to be uploaded. buffer2 := bytes.NewReader([]byte("hello two")) objectName = "testObject2" - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -793,7 +793,7 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *C) { // assert the response status code for expected value 200 OK. c.Assert(response.StatusCode, Equals, http.StatusOK) // fetch the object which was uploaded above. - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -812,7 +812,7 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *C) { // data for new object to be uploaded. buffer3 := bytes.NewReader([]byte("hello three")) objectName = "testObject3" - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buffer3.Len()), buffer3, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -824,7 +824,7 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // fetch the object which was uploaded above. - request, err = newTestSignedRequest("GET", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("GET", getPutObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -843,7 +843,7 @@ func (s *TestSuiteCommon) TestMultipleObjects(c *C) { func (s *TestSuiteCommon) TestNotImplemented(c *C) { // Generate a random bucket name. bucketName := getRandomBucketName() - request, err := newTestSignedRequest("GET", s.endPoint+"/"+bucketName+"/object?policy", + request, err := newTestSignedRequestV4("GET", s.endPoint+"/"+bucketName+"/object?policy", 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -858,7 +858,7 @@ func (s *TestSuiteCommon) TestHeader(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // obtain HTTP request to fetch an object from non-existent bucket/object. - request, err := newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, "testObject"), + request, err := newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, "testObject"), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -882,7 +882,7 @@ func (s *TestSuiteCommon) TestPutBucket(c *C) { go func() { defer wg.Done() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -899,7 +899,7 @@ func (s *TestSuiteCommon) TestPutBucket(c *C) { bucketName = getRandomBucketName() //Block 2: testing for correctness of the functionality // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -920,7 +920,7 @@ func (s *TestSuiteCommon) TestCopyObject(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -934,7 +934,7 @@ func (s *TestSuiteCommon) TestCopyObject(c *C) { buffer1 := bytes.NewReader([]byte("hello world")) objectName := "testObject" // create HTTP request for object upload. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) request.Header.Set("Content-Type", "application/json") c.Assert(err, IsNil) @@ -950,7 +950,7 @@ func (s *TestSuiteCommon) TestCopyObject(c *C) { c.Assert(err, IsNil) // setting the "X-Amz-Copy-Source" to allow copying the content of previously uploaded object. request.Header.Set("X-Amz-Copy-Source", url.QueryEscape("/"+bucketName+"/"+objectName)) - err = signRequest(request, s.accessKey, s.secretKey) + err = signRequestV4(request, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request. // the content is expected to have the content of previous disk. @@ -959,7 +959,7 @@ func (s *TestSuiteCommon) TestCopyObject(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // creating HTTP request to fetch the previously uploaded object. - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName2), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, objectName2), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) // executing the HTTP request. @@ -979,7 +979,7 @@ func (s *TestSuiteCommon) TestPutObject(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -993,7 +993,7 @@ func (s *TestSuiteCommon) TestPutObject(c *C) { buffer1 := bytes.NewReader([]byte("hello world")) objectName := "testObject" // creating HTTP request for object upload. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request for object upload. @@ -1002,7 +1002,7 @@ func (s *TestSuiteCommon) TestPutObject(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // fetch the object back and verify its contents. - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request to fetch the object. @@ -1025,7 +1025,7 @@ func (s *TestSuiteCommon) TestPutObject(c *C) { // Its success verifies the format of the response. func (s *TestSuiteCommon) TestListBuckets(c *C) { // create HTTP request for listing buckets. - request, err := newTestSignedRequest("GET", getListBucketURL(s.endPoint), + request, err := newTestSignedRequestV4("GET", getListBucketURL(s.endPoint), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1048,7 +1048,7 @@ func (s *TestSuiteCommon) TestValidateSignature(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1064,7 +1064,7 @@ func (s *TestSuiteCommon) TestValidateSignature(c *C) { // Create new HTTP request with incorrect secretKey to generate an incorrect signature. secretKey := s.secretKey + "a" - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objName), 0, nil, s.accessKey, secretKey) + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objName), 0, nil, s.accessKey, secretKey) c.Assert(err, IsNil) response, err = client.Do(request) c.Assert(err, IsNil) @@ -1076,7 +1076,7 @@ func (s *TestSuiteCommon) TestSHA256Mismatch(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1092,7 +1092,7 @@ func (s *TestSuiteCommon) TestSHA256Mismatch(c *C) { // Create new HTTP request with incorrect secretKey to generate an incorrect signature. secretKey := s.secretKey + "a" - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objName), 0, nil, s.accessKey, secretKey) + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objName), 0, nil, s.accessKey, secretKey) c.Assert(request.Header.Get("x-amz-content-sha256"), Equals, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") // Set the body to generate signature mismatch. request.Body = ioutil.NopCloser(bytes.NewReader([]byte("Hello, World"))) @@ -1109,7 +1109,7 @@ func (s *TestSuiteCommon) TestPutObjectLongName(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1123,7 +1123,7 @@ func (s *TestSuiteCommon) TestPutObjectLongName(c *C) { // make long object name. longObjName := fmt.Sprintf("%0255d/%0255d/%0255d", 1, 1, 1) // create new HTTP request to insert the object. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, longObjName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, longObjName), int64(buffer.Len()), buffer, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request. @@ -1133,7 +1133,7 @@ func (s *TestSuiteCommon) TestPutObjectLongName(c *C) { // make long object name. longObjName = fmt.Sprintf("%0256d", 1) buffer = bytes.NewReader([]byte("hello world")) - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, longObjName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, longObjName), int64(buffer.Len()), buffer, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1152,7 +1152,7 @@ func (s *TestSuiteCommon) TestNotBeAbleToCreateObjectInNonexistentBucket(c *C) { // preparing for upload by generating the upload URL. objectName := "test-object" - request, err := newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err := newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1174,7 +1174,7 @@ func (s *TestSuiteCommon) TestHeadOnObjectLastModified(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1189,7 +1189,7 @@ func (s *TestSuiteCommon) TestHeadOnObjectLastModified(c *C) { // content for the object to be uploaded. buffer1 := bytes.NewReader([]byte("hello world")) // obtaining URL for uploading the object. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1198,7 +1198,7 @@ func (s *TestSuiteCommon) TestHeadOnObjectLastModified(c *C) { c.Assert(err, IsNil) c.Assert(response.StatusCode, Equals, http.StatusOK) // make HTTP request to obtain object info. - request, err = newTestSignedRequest("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request. @@ -1216,7 +1216,7 @@ func (s *TestSuiteCommon) TestHeadOnObjectLastModified(c *C) { // make HTTP request to obtain object info. // But this time set the "If-Modified-Since" header to be 10 minute more than the actual // last modified time of the object. - request, err = newTestSignedRequest("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) request.Header.Set("If-Modified-Since", t.Add(10*time.Minute).UTC().Format(http.TimeFormat)) @@ -1229,7 +1229,7 @@ func (s *TestSuiteCommon) TestHeadOnObjectLastModified(c *C) { // Again, obtain the object info. // This time setting "If-Unmodified-Since" to a time after the object is modified. // As documented above, expecting http.StatusPreconditionFailed. - request, err = newTestSignedRequest("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) request.Header.Set("If-Unmodified-Since", t.Add(-10*time.Minute).UTC().Format(http.TimeFormat)) @@ -1244,7 +1244,7 @@ func (s *TestSuiteCommon) TestHeadOnBucket(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getHEADBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getHEADBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1254,7 +1254,7 @@ func (s *TestSuiteCommon) TestHeadOnBucket(c *C) { c.Assert(err, IsNil) c.Assert(response.StatusCode, Equals, http.StatusOK) // make HEAD request on the bucket. - request, err = newTestSignedRequest("HEAD", getHEADBucketURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("HEAD", getHEADBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request. @@ -1270,7 +1270,7 @@ func (s *TestSuiteCommon) TestContentTypePersists(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1285,7 +1285,7 @@ func (s *TestSuiteCommon) TestContentTypePersists(c *C) { buffer1 := bytes.NewReader([]byte("hello world")) objectName := "test-object.png" // constructing HTTP request for object upload. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) c.Assert(err, IsNil) request.Header.Set("Content-Type", "image/png") @@ -1297,7 +1297,7 @@ func (s *TestSuiteCommon) TestContentTypePersists(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // Fetching the object info using HEAD request for the object which was uploaded above. - request, err = newTestSignedRequest("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1308,7 +1308,7 @@ func (s *TestSuiteCommon) TestContentTypePersists(c *C) { c.Assert(response.Header.Get("Content-Type"), Equals, "image/png") // Fetching the object itself and then verify the Content-Type header. - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1323,7 +1323,7 @@ func (s *TestSuiteCommon) TestContentTypePersists(c *C) { // Uploading a new object with Content-Type "application/json". objectName = "test-object.json" buffer2 := bytes.NewReader([]byte("hello world")) - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey) c.Assert(err, IsNil) // setting the request header to be application/json. @@ -1335,7 +1335,7 @@ func (s *TestSuiteCommon) TestContentTypePersists(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // Obtain the info of the object which was uploaded above using HEAD request. - request, err = newTestSignedRequest("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) // Execute the HTTP request. @@ -1345,7 +1345,7 @@ func (s *TestSuiteCommon) TestContentTypePersists(c *C) { c.Assert(response.Header.Get("Content-Type"), Equals, "application/json") // Fetch the object and assert whether the Content-Type header persists. - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1362,7 +1362,7 @@ func (s *TestSuiteCommon) TestContentTypePersists(c *C) { func (s *TestSuiteCommon) TestPartialContent(c *C) { bucketName := getRandomBucketName() - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1372,7 +1372,7 @@ func (s *TestSuiteCommon) TestPartialContent(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) buffer1 := bytes.NewReader([]byte("Hello World")) - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, "bar"), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, "bar"), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1382,7 +1382,7 @@ func (s *TestSuiteCommon) TestPartialContent(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // Prepare request - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, "bar"), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, "bar"), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) request.Header.Add("Range", "bytes=6-7") @@ -1403,7 +1403,7 @@ func (s *TestSuiteCommon) TestListObjectsHandler(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1414,7 +1414,7 @@ func (s *TestSuiteCommon) TestListObjectsHandler(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) buffer1 := bytes.NewReader([]byte("Hello World")) - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, "bar"), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, "bar"), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1424,7 +1424,7 @@ func (s *TestSuiteCommon) TestListObjectsHandler(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // create listObjectsV1 request with valid parameters - request, err = newTestSignedRequest("GET", getListObjectsV1URL(s.endPoint, bucketName, "1000"), + request, err = newTestSignedRequestV4("GET", getListObjectsV1URL(s.endPoint, bucketName, "1000"), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) client = http.Client{} @@ -1438,7 +1438,7 @@ func (s *TestSuiteCommon) TestListObjectsHandler(c *C) { c.Assert(strings.Contains(string(getContent), "bar"), Equals, true) // create listObjectsV2 request with valid parameters - request, err = newTestSignedRequest("GET", getListObjectsV2URL(s.endPoint, bucketName, "1000", ""), + request, err = newTestSignedRequestV4("GET", getListObjectsV2URL(s.endPoint, bucketName, "1000", ""), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) client = http.Client{} @@ -1453,7 +1453,7 @@ func (s *TestSuiteCommon) TestListObjectsHandler(c *C) { c.Assert(strings.Contains(string(getContent), ""), Equals, true) // create listObjectsV2 request with valid parameters and fetch-owner activated - request, err = newTestSignedRequest("GET", getListObjectsV2URL(s.endPoint, bucketName, "1000", "true"), + request, err = newTestSignedRequestV4("GET", getListObjectsV2URL(s.endPoint, bucketName, "1000", "true"), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) client = http.Client{} @@ -1476,7 +1476,7 @@ func (s *TestSuiteCommon) TestListObjectsHandlerErrors(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1487,7 +1487,7 @@ func (s *TestSuiteCommon) TestListObjectsHandlerErrors(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // create listObjectsV1 request with invalid value of max-keys parameter. max-keys is set to -2. - request, err = newTestSignedRequest("GET", getListObjectsV1URL(s.endPoint, bucketName, "-2"), + request, err = newTestSignedRequestV4("GET", getListObjectsV1URL(s.endPoint, bucketName, "-2"), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) client = http.Client{} @@ -1498,7 +1498,7 @@ func (s *TestSuiteCommon) TestListObjectsHandlerErrors(c *C) { verifyError(c, response, "InvalidArgument", "Argument maxKeys must be an integer between 0 and 2147483647", http.StatusBadRequest) // create listObjectsV2 request with invalid value of max-keys parameter. max-keys is set to -2. - request, err = newTestSignedRequest("GET", getListObjectsV2URL(s.endPoint, bucketName, "-2", ""), + request, err = newTestSignedRequestV4("GET", getListObjectsV2URL(s.endPoint, bucketName, "-2", ""), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) client = http.Client{} @@ -1517,7 +1517,7 @@ func (s *TestSuiteCommon) TestPutBucketErrors(c *C) { bucketName := getRandomBucketName() // generating a HTTP request to create bucket. // using invalid bucket name. - request, err := newTestSignedRequest("PUT", s.endPoint+"/putbucket-.", + request, err := newTestSignedRequestV4("PUT", s.endPoint+"/putbucket-.", 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1527,7 +1527,7 @@ func (s *TestSuiteCommon) TestPutBucketErrors(c *C) { // expected to fail with error message "InvalidBucketName". verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest) // HTTP request to create the bucket. - request, err = newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1538,7 +1538,7 @@ func (s *TestSuiteCommon) TestPutBucketErrors(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // make HTTP request to create the same bucket again. // expected to fail with error message "BucketAlreadyOwnedByYou". - request, err = newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1549,7 +1549,7 @@ func (s *TestSuiteCommon) TestPutBucketErrors(c *C) { // request for ACL. // Since Minio server doesn't support ACL's the request is expected to fail with "NotImplemented" error message. - request, err = newTestSignedRequest("PUT", s.endPoint+"/"+bucketName+"?acl", + request, err = newTestSignedRequestV4("PUT", s.endPoint+"/"+bucketName+"?acl", 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1562,7 +1562,7 @@ func (s *TestSuiteCommon) TestGetObjectLarge10MiB(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // form HTTP reqest to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1592,7 +1592,7 @@ func (s *TestSuiteCommon) TestGetObjectLarge10MiB(c *C) { objectName := "test-big-object" // create HTTP request for object upload. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buf.Len()), buf, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1604,7 +1604,7 @@ func (s *TestSuiteCommon) TestGetObjectLarge10MiB(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // prepare HTTP requests to download the object. - request, err = newTestSignedRequest("GET", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("GET", getPutObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1626,7 +1626,7 @@ func (s *TestSuiteCommon) TestGetObjectLarge11MiB(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1657,7 +1657,7 @@ func (s *TestSuiteCommon) TestGetObjectLarge11MiB(c *C) { // Put object buf := bytes.NewReader(buffer.Bytes()) // create HTTP request foe object upload. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buf.Len()), buf, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1668,7 +1668,7 @@ func (s *TestSuiteCommon) TestGetObjectLarge11MiB(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // create HTTP request to download the object. - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1695,7 +1695,7 @@ func (s *TestSuiteCommon) TestGetPartialObjectMisAligned(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1727,7 +1727,7 @@ func (s *TestSuiteCommon) TestGetPartialObjectMisAligned(c *C) { objectName := "test-big-file" // HTTP request to upload the object. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buf.Len()), buf, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1757,7 +1757,7 @@ func (s *TestSuiteCommon) TestGetPartialObjectMisAligned(c *C) { } for _, t := range testCases { // HTTP request to download the object. - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) // Get partial content based on the byte range set. @@ -1783,7 +1783,7 @@ func (s *TestSuiteCommon) TestGetPartialObjectLarge11MiB(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1815,7 +1815,7 @@ func (s *TestSuiteCommon) TestGetPartialObjectLarge11MiB(c *C) { buf := bytes.NewReader([]byte(putContent)) // HTTP request to upload the object. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buf.Len()), buf, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1826,7 +1826,7 @@ func (s *TestSuiteCommon) TestGetPartialObjectLarge11MiB(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // HTTP request to download the object. - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) // This range spans into first two blocks. @@ -1851,7 +1851,7 @@ func (s *TestSuiteCommon) TestGetPartialObjectLarge10MiB(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1884,7 +1884,7 @@ func (s *TestSuiteCommon) TestGetPartialObjectLarge10MiB(c *C) { objectName := "test-big-10Mb-file" // HTTP request to upload the object. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buf.Len()), buf, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1896,7 +1896,7 @@ func (s *TestSuiteCommon) TestGetPartialObjectLarge10MiB(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // HTTP request to download the object. - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) // Get partial content based on the byte range set. @@ -1922,7 +1922,7 @@ func (s *TestSuiteCommon) TestGetObjectErrors(c *C) { bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1936,7 +1936,7 @@ func (s *TestSuiteCommon) TestGetObjectErrors(c *C) { // HTTP request to download the object. // Since the specified object doesn't exist in the given bucket, // expected to fail with error message "NoSuchKey" - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1946,7 +1946,7 @@ func (s *TestSuiteCommon) TestGetObjectErrors(c *C) { verifyError(c, response, "NoSuchKey", "The specified key does not exist.", http.StatusNotFound) // request to download an object, but an invalid bucket name is set. - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, "/getobjecterrors-.", objectName), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, "/getobjecterrors-.", objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1962,7 +1962,7 @@ func (s *TestSuiteCommon) TestGetObjectRangeErrors(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1977,7 +1977,7 @@ func (s *TestSuiteCommon) TestGetObjectRangeErrors(c *C) { objectName := "test-object" // HTTP request to upload the object. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -1989,7 +1989,7 @@ func (s *TestSuiteCommon) TestGetObjectRangeErrors(c *C) { c.Assert(response.StatusCode, Equals, http.StatusOK) // HTTP request to download the object. - request, err = newTestSignedRequest("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("GET", getGetObjectURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) // Invalid byte range set. request.Header.Add("Range", "bytes=-0") @@ -2008,7 +2008,7 @@ func (s *TestSuiteCommon) TestObjectMultipartAbort(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -2028,7 +2028,7 @@ func (s *TestSuiteCommon) TestObjectMultipartAbort(c *C) { // and the case where there is only one upload ID. // construct HTTP request to initiate a NewMultipart upload. - request, err = newTestSignedRequest("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -2046,7 +2046,7 @@ func (s *TestSuiteCommon) TestObjectMultipartAbort(c *C) { c.Assert(len(newResponse.UploadID) > 0, Equals, true) // construct HTTP request to initiate a NewMultipart upload. - request, err = newTestSignedRequest("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -2068,7 +2068,7 @@ func (s *TestSuiteCommon) TestObjectMultipartAbort(c *C) { // content for the part to be uploaded. buffer1 := bytes.NewReader([]byte("hello world")) // HTTP request for the part to be uploaded. - request, err = newTestSignedRequest("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "1"), + request, err = newTestSignedRequestV4("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "1"), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request to upload the first part. @@ -2079,7 +2079,7 @@ func (s *TestSuiteCommon) TestObjectMultipartAbort(c *C) { // content for the second part to be uploaded. buffer2 := bytes.NewReader([]byte("hello world")) // HTTP request for the second part to be uploaded. - request, err = newTestSignedRequest("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "2"), + request, err = newTestSignedRequestV4("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "2"), int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request to upload the second part. @@ -2087,7 +2087,7 @@ func (s *TestSuiteCommon) TestObjectMultipartAbort(c *C) { c.Assert(err, IsNil) c.Assert(response2.StatusCode, Equals, http.StatusOK) // HTTP request for aborting the multipart upload. - request, err = newTestSignedRequest("DELETE", getAbortMultipartUploadURL(s.endPoint, bucketName, objectName, uploadID), + request, err = newTestSignedRequestV4("DELETE", getAbortMultipartUploadURL(s.endPoint, bucketName, objectName, uploadID), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request to abort the multipart upload. @@ -2103,7 +2103,7 @@ func (s *TestSuiteCommon) TestBucketMultipartList(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -2115,7 +2115,7 @@ func (s *TestSuiteCommon) TestBucketMultipartList(c *C) { objectName := "test-multipart-object" // construct HTTP request to initiate a NewMultipart upload. - request, err = newTestSignedRequest("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request initiating the new multipart upload. @@ -2137,7 +2137,7 @@ func (s *TestSuiteCommon) TestBucketMultipartList(c *C) { // content for the part to be uploaded. buffer1 := bytes.NewReader([]byte("hello world")) // HTTP request for the part to be uploaded. - request, err = newTestSignedRequest("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "1"), + request, err = newTestSignedRequestV4("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "1"), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request to upload the first part. @@ -2148,7 +2148,7 @@ func (s *TestSuiteCommon) TestBucketMultipartList(c *C) { // content for the second part to be uploaded. buffer2 := bytes.NewReader([]byte("hello world")) // HTTP request for the second part to be uploaded. - request, err = newTestSignedRequest("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "2"), + request, err = newTestSignedRequestV4("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "2"), int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request to upload the second part. @@ -2157,7 +2157,7 @@ func (s *TestSuiteCommon) TestBucketMultipartList(c *C) { c.Assert(response2.StatusCode, Equals, http.StatusOK) // HTTP request to ListMultipart Uploads. - request, err = newTestSignedRequest("GET", getListMultipartURL(s.endPoint, bucketName), + request, err = newTestSignedRequestV4("GET", getListMultipartURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request. @@ -2216,7 +2216,7 @@ func (s *TestSuiteCommon) TestValidateObjectMultipartUploadID(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -2228,7 +2228,7 @@ func (s *TestSuiteCommon) TestValidateObjectMultipartUploadID(c *C) { objectName := "directory1/directory2/object" // construct HTTP request to initiate a NewMultipart upload. - request, err = newTestSignedRequest("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request initiating the new multipart upload. @@ -2252,7 +2252,7 @@ func (s *TestSuiteCommon) TestObjectMultipartListError(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -2264,7 +2264,7 @@ func (s *TestSuiteCommon) TestObjectMultipartListError(c *C) { objectName := "test-multipart-object" // construct HTTP request to initiate a NewMultipart upload. - request, err = newTestSignedRequest("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request initiating the new multipart upload. @@ -2284,7 +2284,7 @@ func (s *TestSuiteCommon) TestObjectMultipartListError(c *C) { // content for the part to be uploaded. buffer1 := bytes.NewReader([]byte("hello world")) // HTTP request for the part to be uploaded. - request, err = newTestSignedRequest("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "1"), + request, err = newTestSignedRequestV4("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "1"), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request to upload the first part. @@ -2295,7 +2295,7 @@ func (s *TestSuiteCommon) TestObjectMultipartListError(c *C) { // content for the second part to be uploaded. buffer2 := bytes.NewReader([]byte("hello world")) // HTTP request for the second part to be uploaded. - request, err = newTestSignedRequest("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "2"), + request, err = newTestSignedRequestV4("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "2"), int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -2306,7 +2306,7 @@ func (s *TestSuiteCommon) TestObjectMultipartListError(c *C) { // HTTP request to ListMultipart Uploads. // max-keys is set to valid value of 1 - request, err = newTestSignedRequest("GET", getListMultipartURLWithParams(s.endPoint, bucketName, objectName, uploadID, "1"), + request, err = newTestSignedRequestV4("GET", getListMultipartURLWithParams(s.endPoint, bucketName, objectName, uploadID, "1"), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request. @@ -2316,7 +2316,7 @@ func (s *TestSuiteCommon) TestObjectMultipartListError(c *C) { // HTTP request to ListMultipart Uploads. // max-keys is set to invalid value of -2. - request, err = newTestSignedRequest("GET", getListMultipartURLWithParams(s.endPoint, bucketName, objectName, uploadID, "-2"), + request, err = newTestSignedRequestV4("GET", getListMultipartURLWithParams(s.endPoint, bucketName, objectName, uploadID, "-2"), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) // execute the HTTP request. @@ -2333,7 +2333,7 @@ func (s *TestSuiteCommon) TestObjectValidMD5(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -2354,7 +2354,7 @@ func (s *TestSuiteCommon) TestObjectValidMD5(c *C) { buffer1 := bytes.NewReader(data) objectName := "test-1-object" // HTTP request for the object to be uploaded. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) c.Assert(err, IsNil) // set the Content-Md5 to be the hash to content. @@ -2367,7 +2367,7 @@ func (s *TestSuiteCommon) TestObjectValidMD5(c *C) { objectName = "test-2-object" buffer1 = bytes.NewReader(data) // HTTP request for the object to be uploaded. - request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) c.Assert(err, IsNil) // set Content-Md5 to invalid value. @@ -2386,7 +2386,7 @@ func (s *TestSuiteCommon) TestObjectMultipart(c *C) { // generate a random bucket name. bucketName := getRandomBucketName() // HTTP request to create the bucket. - request, err := newTestSignedRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + request, err := newTestSignedRequestV4("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -2398,7 +2398,7 @@ func (s *TestSuiteCommon) TestObjectMultipart(c *C) { objectName := "test-multipart-object" // construct HTTP request to initiate a NewMultipart upload. - request, err = newTestSignedRequest("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), + request, err = newTestSignedRequestV4("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), 0, nil, s.accessKey, s.secretKey) c.Assert(err, IsNil) @@ -2428,7 +2428,7 @@ func (s *TestSuiteCommon) TestObjectMultipart(c *C) { buffer1 := bytes.NewReader(data) // HTTP request for the part to be uploaded. - request, err = newTestSignedRequest("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "1"), + request, err = newTestSignedRequestV4("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "1"), int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) // set the Content-Md5 header to the base64 encoding the md5Sum of the content. request.Header.Set("Content-Md5", base64.StdEncoding.EncodeToString(md5Sum)) @@ -2451,7 +2451,7 @@ func (s *TestSuiteCommon) TestObjectMultipart(c *C) { buffer2 := bytes.NewReader(data) // HTTP request for the second part to be uploaded. - request, err = newTestSignedRequest("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "2"), + request, err = newTestSignedRequestV4("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "2"), int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey) // set the Content-Md5 header to the base64 encoding the md5Sum of the content. request.Header.Set("Content-Md5", base64.StdEncoding.EncodeToString(md5Sum)) @@ -2480,7 +2480,7 @@ func (s *TestSuiteCommon) TestObjectMultipart(c *C) { completeBytes, err := xml.Marshal(completeUploads) c.Assert(err, IsNil) // Indicating that all parts are uploaded and initiating completeMultipartUpload. - request, err = newTestSignedRequest("POST", getCompleteMultipartUploadURL(s.endPoint, bucketName, objectName, uploadID), + request, err = newTestSignedRequestV4("POST", getCompleteMultipartUploadURL(s.endPoint, bucketName, objectName, uploadID), int64(len(completeBytes)), bytes.NewReader(completeBytes), s.accessKey, s.secretKey) c.Assert(err, IsNil) // Execute the complete multipart request. diff --git a/cmd/server_v2_test.go b/cmd/server_v2_test.go new file mode 100644 index 000000000..391789c53 --- /dev/null +++ b/cmd/server_v2_test.go @@ -0,0 +1,2453 @@ +/* + * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "bytes" + "crypto/md5" + "encoding/base64" + "encoding/hex" + "encoding/xml" + "fmt" + "io" + "io/ioutil" + "math/rand" + "net/http" + "net/url" + "strings" + "sync" + "time" + + . "gopkg.in/check.v1" +) + +// API suite container common to both FS and XL. +type TestSuiteCommonV2 struct { + serverType string + testServer TestServer + endPoint string + accessKey string + secretKey string +} + +// Init and run test on FS backend. +var _ = Suite(&TestSuiteCommonV2{serverType: "FS"}) + +// Setting up the test suite. +// Starting the Test server with temporary FS backend. +func (s *TestSuiteCommonV2) SetUpSuite(c *C) { + s.testServer = StartTestServer(c, s.serverType) + s.endPoint = s.testServer.Server.URL + s.accessKey = s.testServer.AccessKey + s.secretKey = s.testServer.SecretKey +} + +// Called implicitly by "gopkg.in/check.v1" after all tests are run. +func (s *TestSuiteCommonV2) TearDownSuite(c *C) { + s.testServer.Stop() +} + +func (s *TestSuiteCommonV2) TestAuth(c *C) { + secretID, err := genSecretAccessKey() + c.Assert(err, IsNil) + + accessID, err := genAccessKeyID() + c.Assert(err, IsNil) + + c.Assert(len(secretID), Equals, minioSecretID) + c.Assert(len(accessID), Equals, minioAccessID) +} + +func (s *TestSuiteCommonV2) TestBucketSQSNotification(c *C) { + // Sample bucket notification. + bucketNotificationBuf := `s3:ObjectCreated:Putprefiximages/1arn:minio:sqs:us-east-1:444455556666:amqp` + // generate a random bucket Name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the request. + response, err := client.Do(request) + c.Assert(err, IsNil) + + // assert the http response status code. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + request, err = newTestSignedRequestV2("PUT", getPutNotificationURL(s.endPoint, bucketName), + int64(len(bucketNotificationBuf)), bytes.NewReader([]byte(bucketNotificationBuf)), s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + + c.Assert(err, IsNil) + verifyError(c, response, "InvalidArgument", "A specified destination ARN does not exist or is not well-formed. Verify the destination ARN.", http.StatusBadRequest) +} + +// TestBucketNotification - Inserts the bucket notification and verifies it by fetching the notification back. +func (s *TestSuiteCommonV2) TestBucketSNSNotification(c *C) { + // Sample bucket notification. + bucketNotificationBuf := `s3:ObjectCreated:Putprefiximages/1arn:minio:sns:us-east-1:444455556666:listen` + + // generate a random bucket Name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the request. + response, err := client.Do(request) + c.Assert(err, IsNil) + // assert the http response status code. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + request, err = newTestSignedRequestV2("PUT", getPutNotificationURL(s.endPoint, bucketName), + int64(len(bucketNotificationBuf)), bytes.NewReader([]byte(bucketNotificationBuf)), s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // Fetch the uploaded policy. + request, err = newTestSignedRequestV2("GET", getGetNotificationURL(s.endPoint, bucketName), 0, nil, + s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + bucketNotificationReadBuf, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + // Verify if downloaded policy matches with previousy uploaded. + c.Assert(bytes.Equal([]byte(bucketNotificationBuf), bucketNotificationReadBuf), Equals, true) + + invalidBucketNotificationBuf := `s3:ObjectCreated:Putinvalidimages/1arn:minio:sns:us-east-1:444455556666:minio` + + request, err = newTestSignedRequestV2("PUT", getPutNotificationURL(s.endPoint, bucketName), + int64(len(invalidBucketNotificationBuf)), bytes.NewReader([]byte(invalidBucketNotificationBuf)), s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + + verifyError(c, response, "InvalidArgument", "A specified destination ARN does not exist or is not well-formed. Verify the destination ARN.", http.StatusBadRequest) + + invalidBucketNotificationBuf = `s3:ObjectCreated:Putinvalidimages/1arn:minio:sns:us-east-1:1:listen` + + request, err = newTestSignedRequestV2("PUT", getPutNotificationURL(s.endPoint, bucketName), + int64(len(invalidBucketNotificationBuf)), bytes.NewReader([]byte(invalidBucketNotificationBuf)), s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + + verifyError(c, response, "InvalidArgument", "filter rule name must be either prefix or suffix", http.StatusBadRequest) + + invalidBucketNotificationBuf = `s3:ObjectCreated:Putprefix|||1arn:minio:sns:us-east-1:1:listen` + + request, err = newTestSignedRequestV2("PUT", getPutNotificationURL(s.endPoint, bucketName), + int64(len(invalidBucketNotificationBuf)), bytes.NewReader([]byte(invalidBucketNotificationBuf)), s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + + verifyError(c, response, "InvalidArgument", "Size of filter rule value cannot exceed 1024 bytes in UTF-8 representation", http.StatusBadRequest) + + invalidBucketNotificationBuf = `s3:ObjectCreated:Putprefiximages/1arn:minio:sns:us-west-1:444455556666:listen` + request, err = newTestSignedRequestV2("PUT", getPutNotificationURL(s.endPoint, bucketName), + int64(len(invalidBucketNotificationBuf)), bytes.NewReader([]byte(invalidBucketNotificationBuf)), s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + + verifyError(c, response, "InvalidArgument", "A specified destination is in a different region than the bucket. You must use a destination that resides in the same region as the bucket.", http.StatusBadRequest) + + invalidBucketNotificationBuf = `s3:ObjectCreated:Invalidprefiximages/1arn:minio:sns:us-east-1:444455556666:listen` + request, err = newTestSignedRequestV2("PUT", getPutNotificationURL(s.endPoint, bucketName), + int64(len(invalidBucketNotificationBuf)), bytes.NewReader([]byte(invalidBucketNotificationBuf)), s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + verifyError(c, response, "InvalidArgument", "A specified event is not supported for notifications.", http.StatusBadRequest) + + bucketNotificationDuplicates := `s3:ObjectCreated:Putprefiximages/1arn:minio:sns:us-east-1:444455556666:listens3:ObjectCreated:Putprefiximages/1arn:minio:sns:us-east-1:444455556666:listen` + request, err = newTestSignedRequestV2("PUT", getPutNotificationURL(s.endPoint, bucketName), + int64(len(bucketNotificationDuplicates)), bytes.NewReader([]byte(bucketNotificationDuplicates)), s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + verifyError(c, response, "InvalidArgument", "Configurations overlap. Configurations on the same bucket cannot share a common event type.", http.StatusBadRequest) +} + +// TestBucketPolicy - Inserts the bucket policy and verifies it by fetching the policy back. +// Deletes the policy and verifies the deletion by fetching it back. +func (s *TestSuiteCommonV2) TestBucketPolicy(c *C) { + // Sample bucket policy. + bucketPolicyBuf := `{"Version":"2012-10-17","Statement":[{"Action":["s3:GetBucketLocation","s3:ListBucket"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s"],"Sid":""},{"Action":["s3:GetObject"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s/this*"],"Sid":""}]}` + + // generate a random bucket Name. + bucketName := getRandomBucketName() + // create the policy statement string with the randomly generated bucket name. + bucketPolicyStr := fmt.Sprintf(bucketPolicyBuf, bucketName, bucketName) + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the request. + response, err := client.Do(request) + c.Assert(err, IsNil) + // assert the http response status code. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + /// Put a new bucket policy. + request, err = newTestSignedRequestV2("PUT", getPutPolicyURL(s.endPoint, bucketName), + int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request to create bucket. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusNoContent) + + // Fetch the uploaded policy. + request, err = newTestSignedRequestV2("GET", getGetPolicyURL(s.endPoint, bucketName), 0, nil, + s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + bucketPolicyReadBuf, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + // Verify if downloaded policy matches with previousy uploaded. + c.Assert(bytes.Equal([]byte(bucketPolicyStr), bucketPolicyReadBuf), Equals, true) + + // Delete policy. + request, err = newTestSignedRequestV2("DELETE", getDeletePolicyURL(s.endPoint, bucketName), 0, nil, + s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusNoContent) + + // Verify if the policy was indeed deleted. + request, err = newTestSignedRequestV2("GET", getGetPolicyURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusNotFound) +} + +// TestDeleteBucket - validates DELETE bucket operation. +func (s *TestSuiteCommonV2) TestDeleteBucket(c *C) { + bucketName := getRandomBucketName() + + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + response, err := client.Do(request) + c.Assert(err, IsNil) + // assert the response status code. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // construct request to delete the bucket. + request, err = newTestSignedRequestV2("DELETE", getDeleteBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + // Assert the response status code. + c.Assert(response.StatusCode, Equals, http.StatusNoContent) +} + +// TestDeleteBucketNotEmpty - Validates the operation during an attempt to delete a non-empty bucket. +func (s *TestSuiteCommonV2) TestDeleteBucketNotEmpty(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the request. + response, err := client.Do(request) + c.Assert(err, IsNil) + // assert the response status code. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // generate http request for an object upload. + // "test-object" is the object name. + objectName := "test-object" + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the request to complete object upload. + response, err = client.Do(request) + c.Assert(err, IsNil) + // assert the status code of the response. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // constructing http request to delete the bucket. + // making an attempt to delete an non-empty bucket. + // expected to fail. + request, err = newTestSignedRequestV2("DELETE", getDeleteBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusConflict) + +} + +// Test deletes multple objects and verifies server resonse. +func (s *TestSuiteCommonV2) TestDeleteMultipleObjects(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the request. + response, err := client.Do(request) + c.Assert(err, IsNil) + // assert the http response status code. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + objectName := "prefix/myobject" + delObjReq := DeleteObjectsRequest{ + Quiet: false, + } + for i := 0; i < 10; i++ { + // Obtain http request to upload object. + // object Name contains a prefix. + objName := fmt.Sprintf("%d/%s", i, objectName) + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the http request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // assert the status of http response. + c.Assert(response.StatusCode, Equals, http.StatusOK) + // Append all objects. + delObjReq.Objects = append(delObjReq.Objects, ObjectIdentifier{ + ObjectName: objName, + }) + } + // Marshal delete request. + deleteReqBytes, err := xml.Marshal(delObjReq) + c.Assert(err, IsNil) + + // Delete list of objects. + request, err = newTestSignedRequestV2("POST", getMultiDeleteObjectURL(s.endPoint, bucketName), + int64(len(deleteReqBytes)), bytes.NewReader(deleteReqBytes), s.accessKey, s.secretKey) + c.Assert(err, IsNil) + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + var deleteResp = DeleteObjectsResponse{} + delRespBytes, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + err = xml.Unmarshal(delRespBytes, &deleteResp) + c.Assert(err, IsNil) + for i := 0; i < 10; i++ { + // All the objects should be under deleted list (including non-existent object) + c.Assert(deleteResp.DeletedObjects[i], DeepEquals, delObjReq.Objects[i]) + } + c.Assert(len(deleteResp.Errors), Equals, 0) + + // Attempt second time results should be same, NoSuchKey for objects not found + // shouldn't be set. + request, err = newTestSignedRequestV2("POST", getMultiDeleteObjectURL(s.endPoint, bucketName), + int64(len(deleteReqBytes)), bytes.NewReader(deleteReqBytes), s.accessKey, s.secretKey) + c.Assert(err, IsNil) + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + deleteResp = DeleteObjectsResponse{} + delRespBytes, err = ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + err = xml.Unmarshal(delRespBytes, &deleteResp) + c.Assert(err, IsNil) + for i := 0; i < 10; i++ { + c.Assert(deleteResp.DeletedObjects[i], DeepEquals, delObjReq.Objects[i]) + } + c.Assert(len(deleteResp.Errors), Equals, 0) +} + +// Tests delete object responses and success. +func (s *TestSuiteCommonV2) TestDeleteObject(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the request. + response, err := client.Do(request) + c.Assert(err, IsNil) + // assert the http response status code. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + objectName := "prefix/myobject" + // obtain http request to upload object. + // object Name contains a prefix. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the http request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // assert the status of http response. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // object name was "prefix/myobject", an attempt to delelte "prefix" + // Should not delete "prefix/myobject" + request, err = newTestSignedRequestV2("DELETE", getDeleteObjectURL(s.endPoint, bucketName, "prefix"), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusNoContent) + + // create http request to HEAD on the object. + // this helps to validate the existence of the bucket. + request, err = newTestSignedRequestV2("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + // Assert the HTTP response status code. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // create HTTP request to delete the object. + request, err = newTestSignedRequestV2("DELETE", getDeleteObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + client = http.Client{} + // execute the http request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // assert the http response status code. + c.Assert(response.StatusCode, Equals, http.StatusNoContent) + + // Delete of non-existent data should return success. + request, err = newTestSignedRequestV2("DELETE", getDeleteObjectURL(s.endPoint, bucketName, "prefix/myobject1"), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + client = http.Client{} + // execute the http request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // assert the http response status. + c.Assert(response.StatusCode, Equals, http.StatusNoContent) +} + +// TestNonExistentBucket - Asserts response for HEAD on non-existent bucket. +func (s *TestSuiteCommonV2) TestNonExistentBucket(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // create request to HEAD on the bucket. + // HEAD on an bucket helps validate the existence of the bucket. + request, err := newTestSignedRequestV2("HEAD", getHEADBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the http request. + response, err := client.Do(request) + c.Assert(err, IsNil) + // Assert the response. + c.Assert(response.StatusCode, Equals, http.StatusNotFound) +} + +// TestEmptyObject - Asserts the response for operation on a 0 byte object. +func (s *TestSuiteCommonV2) TestEmptyObject(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the http request. + response, err := client.Do(request) + c.Assert(err, IsNil) + // assert the http response status code. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + objectName := "test-object" + // construct http request for uploading the object. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the upload request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // assert the http response. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // make HTTP request to fetch the object. + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the http request to fetch object. + response, err = client.Do(request) + c.Assert(err, IsNil) + // assert the http response status code. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + var buffer bytes.Buffer + // extract the body of the response. + responseBody, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + // assert the http response body content. + c.Assert(true, Equals, bytes.Equal(responseBody, buffer.Bytes())) +} + +func (s *TestSuiteCommonV2) TestBucket(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + request, err = newTestSignedRequestV2("HEAD", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) +} + +// Tests get anonymous object. +func (s *TestSuiteCommonV2) TestObjectGetAnonymous(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + buffer := bytes.NewReader([]byte("hello world")) + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the make bucket http request. + response, err := client.Do(request) + c.Assert(err, IsNil) + // assert the response http status code. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + objectName := "testObject" + // create HTTP request to upload the object. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buffer.Len()), buffer, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request to upload the object. + response, err = client.Do(request) + c.Assert(err, IsNil) + // assert the HTTP response status code. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // initiate anonymous HTTP request to fetch the object which does not exist. We need to return AccessDenied. + response, err = http.Get(getGetObjectURL(s.endPoint, bucketName, objectName+".1")) + c.Assert(err, IsNil) + // assert the http response status code. + verifyError(c, response, "AccessDenied", "Access Denied.", http.StatusForbidden) + + // initiate anonymous HTTP request to fetch the object which does exist. We need to return AccessDenied. + response, err = http.Get(getGetObjectURL(s.endPoint, bucketName, objectName)) + c.Assert(err, IsNil) + // assert the http response status code. + verifyError(c, response, "AccessDenied", "Access Denied.", http.StatusForbidden) +} + +// TestGetObject - Tests fetching of a small object after its insertion into the bucket. +func (s *TestSuiteCommonV2) TestObjectGet(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + buffer := bytes.NewReader([]byte("hello world")) + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the make bucket http request. + response, err := client.Do(request) + c.Assert(err, IsNil) + // assert the response http status code. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + objectName := "testObject" + // create HTTP request to upload the object. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buffer.Len()), buffer, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request to upload the object. + response, err = client.Do(request) + c.Assert(err, IsNil) + // assert the HTTP response status code. + c.Assert(response.StatusCode, Equals, http.StatusOK) + // concurrently reading the object, safety check for races. + var wg sync.WaitGroup + for i := 0; i < testConcurrencyLevel; i++ { + wg.Add(1) + go func() { + defer wg.Done() + // HTTP request to create the bucket. + // create HTTP request to fetch the object. + getRequest, err := newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + reqClient := http.Client{} + // execute the http request to fetch the object. + getResponse, err := reqClient.Do(getRequest) + c.Assert(err, IsNil) + defer getResponse.Body.Close() + // assert the http response status code. + c.Assert(getResponse.StatusCode, Equals, http.StatusOK) + + // extract response body content. + responseBody, err := ioutil.ReadAll(getResponse.Body) + c.Assert(err, IsNil) + // assert the HTTP response body content with the expected content. + c.Assert(responseBody, DeepEquals, []byte("hello world")) + }() + + } + wg.Wait() +} + +// TestMultipleObjects - Validates upload and fetching of multiple object into the bucket. +func (s *TestSuiteCommonV2) TestMultipleObjects(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create the bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // constructing HTTP request to fetch a non-existent object. + // expected to fail, error response asserted for expected error values later. + objectName := "testObject" + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // Asserting the error response with the expected values. + verifyError(c, response, "NoSuchKey", "The specified key does not exist.", http.StatusNotFound) + + objectName = "testObject1" + // content for the object to be uploaded. + buffer1 := bytes.NewReader([]byte("hello one")) + // create HTTP request for the object upload. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request for object upload. + response, err = client.Do(request) + c.Assert(err, IsNil) + // assert the returned values. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // create HTTP request to fetch the object which was uploaded above. + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // assert whether 200 OK response status is obtained. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // extract the response body. + responseBody, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + // assert the content body for the expected object data. + c.Assert(true, Equals, bytes.Equal(responseBody, []byte("hello one"))) + + // data for new object to be uploaded. + buffer2 := bytes.NewReader([]byte("hello two")) + objectName = "testObject2" + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request for object upload. + response, err = client.Do(request) + c.Assert(err, IsNil) + // assert the response status code for expected value 200 OK. + c.Assert(response.StatusCode, Equals, http.StatusOK) + // fetch the object which was uploaded above. + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request to fetch the object. + response, err = client.Do(request) + c.Assert(err, IsNil) + // assert the response status code for expected value 200 OK. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // verify response data + responseBody, err = ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + c.Assert(true, Equals, bytes.Equal(responseBody, []byte("hello two"))) + + // data for new object to be uploaded. + buffer3 := bytes.NewReader([]byte("hello three")) + objectName = "testObject3" + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buffer3.Len()), buffer3, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // verify the response code with the expected value of 200 OK. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // fetch the object which was uploaded above. + request, err = newTestSignedRequestV2("GET", getPutObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // verify object. + responseBody, err = ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + c.Assert(true, Equals, bytes.Equal(responseBody, []byte("hello three"))) +} + +// TestNotImplemented - validates if object policy is implemented, should return 'NotImplemented'. +func (s *TestSuiteCommonV2) TestNotImplemented(c *C) { + // Generate a random bucket name. + bucketName := getRandomBucketName() + request, err := newTestSignedRequestV2("GET", s.endPoint+"/"+bucketName+"/object?policy", + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusNotImplemented) +} + +// TestHeader - Validates the error response for an attempt to fetch non-existent object. +func (s *TestSuiteCommonV2) TestHeader(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // obtain HTTP request to fetch an object from non-existent bucket/object. + request, err := newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, "testObject"), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + response, err := client.Do(request) + c.Assert(err, IsNil) + // asserting for the expected error response. + verifyError(c, response, "NoSuchBucket", "The specified bucket does not exist", http.StatusNotFound) +} + +func (s *TestSuiteCommonV2) TestPutBucket(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // Block 1: Testing for racey access + // The assertion is removed from this block since the purpose of this block is to find races + // The purpose this block is not to check for correctness of functionality + // Run the test with -race flag to utilize this + var wg sync.WaitGroup + for i := 0; i < testConcurrencyLevel; i++ { + wg.Add(1) + go func() { + defer wg.Done() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + response, err := client.Do(request) + if err != nil { + c.Fatalf("Put bucket Failed: %s", err) + } + defer response.Body.Close() + }() + } + wg.Wait() + + bucketName = getRandomBucketName() + //Block 2: testing for correctness of the functionality + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + response.Body.Close() +} + +// TestCopyObject - Validates copy object. +// The following is the test flow. +// 1. Create bucket. +// 2. Insert Object. +// 3. Use "X-Amz-Copy-Source" header to copy the previously created object. +// 4. Validate the content of copied object. +func (s *TestSuiteCommonV2) TestCopyObject(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // content for the object to be created. + buffer1 := bytes.NewReader([]byte("hello world")) + objectName := "testObject" + // create HTTP request for object upload. + request, err = newTestSignedRequestV2ContentType("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey, "application/json") + c.Assert(err, IsNil) + // execute the HTTP request for object upload. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + objectName2 := "testObject2" + // Unlike the actual PUT object request, the request to Copy Object doesn't contain request body, + // empty body with the "X-Amz-Copy-Source" header pointing to the object to copies it in the backend. + request, err = newTestRequest("PUT", getPutObjectURL(s.endPoint, bucketName, objectName2), 0, nil) + c.Assert(err, IsNil) + // setting the "X-Amz-Copy-Source" to allow copying the content of previously uploaded object. + request.Header.Set("X-Amz-Copy-Source", url.QueryEscape("/"+bucketName+"/"+objectName)) + err = signRequestV2(request, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request. + // the content is expected to have the content of previous disk. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // creating HTTP request to fetch the previously uploaded object. + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, objectName2), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // executing the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // validating the response status code. + c.Assert(response.StatusCode, Equals, http.StatusOK) + // reading the response body. + // response body is expected to have the copied content of the first uploaded object. + object, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + c.Assert(string(object), Equals, "hello world") +} + +// TestPutObject - Tests successful put object request. +func (s *TestSuiteCommonV2) TestPutObject(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // content for new object upload. + buffer1 := bytes.NewReader([]byte("hello world")) + objectName := "testObject" + // creating HTTP request for object upload. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request for object upload. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // fetch the object back and verify its contents. + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request to fetch the object. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + c.Assert(response.ContentLength, Equals, int64(len([]byte("hello world")))) + var buffer2 bytes.Buffer + // retrive the contents of response body. + n, err := io.Copy(&buffer2, response.Body) + c.Assert(err, IsNil) + c.Assert(n, Equals, int64(len([]byte("hello world")))) + // asserted the contents of the fetched object with the expected result. + c.Assert(true, Equals, bytes.Equal(buffer2.Bytes(), []byte("hello world"))) + +} + +// TestListBuckets - Make request for listing of all buckets. +// XML response is parsed. +// Its success verifies the format of the response. +func (s *TestSuiteCommonV2) TestListBuckets(c *C) { + // create HTTP request for listing buckets. + request, err := newTestSignedRequestV2("GET", getListBucketURL(s.endPoint), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to list buckets. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + var results ListBucketsResponse + // parse the list bucket response. + decoder := xml.NewDecoder(response.Body) + err = decoder.Decode(&results) + // validating that the xml-decoding/parsing was successful. + c.Assert(err, IsNil) +} + +// This tests validate if PUT handler can successfully detect signature mismatch. +func (s *TestSuiteCommonV2) TestValidateSignature(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // Execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + objName := "test-object" + + // Body is on purpose set to nil so that we get payload generated for empty bytes. + + // Create new HTTP request with incorrect secretKey to generate an incorrect signature. + secretKey := s.secretKey + "a" + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objName), 0, nil, s.accessKey, secretKey) + c.Assert(err, IsNil) + response, err = client.Do(request) + c.Assert(err, IsNil) + verifyError(c, response, "SignatureDoesNotMatch", "The request signature we calculated does not match the signature you provided. Check your key and signing method.", http.StatusForbidden) +} + +// TestNotBeAbleToCreateObjectInNonexistentBucket - Validates the error response +// on an attempt to upload an object into a non-existent bucket. +func (s *TestSuiteCommonV2) TestPutObjectLongName(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // Execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + // Content for the object to be uploaded. + buffer := bytes.NewReader([]byte("hello world")) + // make long object name. + longObjName := fmt.Sprintf("%0255d/%0255d/%0255d", 1, 1, 1) + // create new HTTP request to insert the object. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, longObjName), + int64(buffer.Len()), buffer, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + // make long object name. + longObjName = fmt.Sprintf("%0256d", 1) + buffer = bytes.NewReader([]byte("hello world")) + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, longObjName), + int64(buffer.Len()), buffer, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + response, err = client.Do(request) + c.Assert(err, IsNil) + verifyError(c, response, "XMinioInvalidObjectName", "Object name contains unsupported characters. Unsupported characters are `^*|\\\"", http.StatusBadRequest) +} + +// TestNotBeAbleToCreateObjectInNonexistentBucket - Validates the error response +// on an attempt to upload an object into a non-existent bucket. +func (s *TestSuiteCommonV2) TestNotBeAbleToCreateObjectInNonexistentBucket(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // content of the object to be uploaded. + buffer1 := bytes.NewReader([]byte("hello world")) + + // preparing for upload by generating the upload URL. + objectName := "test-object" + request, err := newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // Execute the HTTP request. + response, err := client.Do(request) + c.Assert(err, IsNil) + // Assert the response error message. + verifyError(c, response, "NoSuchBucket", "The specified bucket does not exist", http.StatusNotFound) +} + +// TestHeadOnObjectLastModified - Asserts response for HEAD on an object. +// HEAD requests on an object validates the existence of the object. +// The responses for fetching the object when If-Modified-Since +// and If-Unmodified-Since headers set are validated. +// If-Modified-Since - Return the object only if it has been modified since the specified time, else return a 304 (not modified). +// If-Unmodified-Since - Return the object only if it has not been modified since the specified time, else return a 412 (precondition failed). +func (s *TestSuiteCommonV2) TestHeadOnObjectLastModified(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // preparing for object upload. + objectName := "test-object" + // content for the object to be uploaded. + buffer1 := bytes.NewReader([]byte("hello world")) + // obtaining URL for uploading the object. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + // executing the HTTP request to download the object. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + // make HTTP request to obtain object info. + request, err = newTestSignedRequestV2("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // verify the status of the HTTP response. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // retrive the info of last modification time of the object from the response header. + lastModified := response.Header.Get("Last-Modified") + // Parse it into time.Time structure. + t, err := time.Parse(http.TimeFormat, lastModified) + c.Assert(err, IsNil) + + // make HTTP request to obtain object info. + // But this time set the "If-Modified-Since" header to be 10 minute more than the actual + // last modified time of the object. + request, err = newTestSignedRequestV2("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + request.Header.Set("If-Modified-Since", t.Add(10*time.Minute).UTC().Format(http.TimeFormat)) + response, err = client.Do(request) + c.Assert(err, IsNil) + // Since the "If-Modified-Since" header was ahead in time compared to the actual + // modified time of the object expecting the response status to be http.StatusNotModified. + c.Assert(response.StatusCode, Equals, http.StatusNotModified) + + // Again, obtain the object info. + // This time setting "If-Unmodified-Since" to a time after the object is modified. + // As documented above, expecting http.StatusPreconditionFailed. + request, err = newTestSignedRequestV2("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + request.Header.Set("If-Unmodified-Since", t.Add(-10*time.Minute).UTC().Format(http.TimeFormat)) + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusPreconditionFailed) +} + +// TestHeadOnBucket - Validates response for HEAD on the bucket. +// HEAD request on the bucket validates the existence of the bucket. +func (s *TestSuiteCommonV2) TestHeadOnBucket(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getHEADBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + // make HEAD request on the bucket. + request, err = newTestSignedRequestV2("HEAD", getHEADBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // Asserting the response status for expected value of http.StatusOK. + c.Assert(response.StatusCode, Equals, http.StatusOK) +} + +// TestContentTypePersists - Object upload with different Content-type is first done. +// And then a HEAD and GET request on these objects are done to validate if the same Content-Type set during upload persists. +func (s *TestSuiteCommonV2) TestContentTypePersists(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // Uploading a new object with Content-Type "image/png". + // content for the object to be uploaded. + buffer1 := bytes.NewReader([]byte("hello world")) + objectName := "test-object.png" + // constructing HTTP request for object upload. + request, err = newTestSignedRequestV2ContentType("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey, "image/png") + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request for object upload. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // Fetching the object info using HEAD request for the object which was uploaded above. + request, err = newTestSignedRequestV2("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + // Execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // Verify if the Content-Type header is set during the object persists. + c.Assert(response.Header.Get("Content-Type"), Equals, "image/png") + + // Fetching the object itself and then verify the Content-Type header. + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // Execute the HTTP to fetch the object. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + // Verify if the Content-Type header is set during the object persists. + c.Assert(response.Header.Get("Content-Type"), Equals, "image/png") + + // Uploading a new object with Content-Type "application/json". + objectName = "test-object.json" + buffer2 := bytes.NewReader([]byte("hello world")) + request, err = newTestSignedRequestV2ContentType("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey, "application/json") + c.Assert(err, IsNil) + + // Execute the HTTP request to upload the object. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // Obtain the info of the object which was uploaded above using HEAD request. + request, err = newTestSignedRequestV2("HEAD", getHeadObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // Execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // Assert if the content-type header set during the object upload persists. + c.Assert(response.Header.Get("Content-Type"), Equals, "application/json") + + // Fetch the object and assert whether the Content-Type header persists. + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + // Execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // Assert if the content-type header set during the object upload persists. + c.Assert(response.Header.Get("Content-Type"), Equals, "application/json") +} + +// TestPartialContent - Validating for GetObject with partial content request. +// By setting the Range header, A request to send specific bytes range of data from an +// already uploaded object can be done. +func (s *TestSuiteCommonV2) TestPartialContent(c *C) { + bucketName := getRandomBucketName() + + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + buffer1 := bytes.NewReader([]byte("Hello World")) + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, "bar"), + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // Prepare request + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, "bar"), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + request.Header.Add("Range", "bytes=6-7") + + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusPartialContent) + partialObject, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + + c.Assert(string(partialObject), Equals, "Wo") +} + +// TestListObjectsHandler - Setting valid parameters to List Objects +// and then asserting the response with the expected one. +func (s *TestSuiteCommonV2) TestListObjectsHandler(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + buffer1 := bytes.NewReader([]byte("Hello World")) + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, "bar"), + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // create listObjectsV1 request with valid parameters + request, err = newTestSignedRequestV2("GET", getListObjectsV1URL(s.endPoint, bucketName, "1000"), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + getContent, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + c.Assert(strings.Contains(string(getContent), "bar"), Equals, true) + + // create listObjectsV2 request with valid parameters + request, err = newTestSignedRequestV2("GET", getListObjectsV2URL(s.endPoint, bucketName, "1000", ""), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + getContent, err = ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + c.Assert(strings.Contains(string(getContent), "bar"), Equals, true) + c.Assert(strings.Contains(string(getContent), ""), Equals, true) + + // create listObjectsV2 request with valid parameters and fetch-owner activated + request, err = newTestSignedRequestV2("GET", getListObjectsV2URL(s.endPoint, bucketName, "1000", "true"), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + getContent, err = ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + + c.Assert(strings.Contains(string(getContent), "bar"), Equals, true) + c.Assert(strings.Contains(string(getContent), "miniominio"), Equals, true) + +} + +// TestListObjectsHandlerErrors - Setting invalid parameters to List Objects +// and then asserting the error response with the expected one. +func (s *TestSuiteCommonV2) TestListObjectsHandlerErrors(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // create listObjectsV1 request with invalid value of max-keys parameter. max-keys is set to -2. + request, err = newTestSignedRequestV2("GET", getListObjectsV1URL(s.endPoint, bucketName, "-2"), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // validating the error response. + verifyError(c, response, "InvalidArgument", "Argument maxKeys must be an integer between 0 and 2147483647", http.StatusBadRequest) + + // create listObjectsV2 request with invalid value of max-keys parameter. max-keys is set to -2. + request, err = newTestSignedRequestV2("GET", getListObjectsV2URL(s.endPoint, bucketName, "-2", ""), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // validating the error response. + verifyError(c, response, "InvalidArgument", "Argument maxKeys must be an integer between 0 and 2147483647", http.StatusBadRequest) + +} + +// TestPutBucketErrors - request for non valid bucket operation +// and validate it with expected error result. +func (s *TestSuiteCommonV2) TestPutBucketErrors(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // generating a HTTP request to create bucket. + // using invalid bucket name. + request, err := newTestSignedRequestV2("PUT", s.endPoint+"/putbucket-.", + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + response, err := client.Do(request) + c.Assert(err, IsNil) + // expected to fail with error message "InvalidBucketName". + verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest) + // HTTP request to create the bucket. + request, err = newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request to create bucket. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + // make HTTP request to create the same bucket again. + // expected to fail with error message "BucketAlreadyOwnedByYou". + request, err = newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + response, err = client.Do(request) + c.Assert(err, IsNil) + verifyError(c, response, "BucketAlreadyOwnedByYou", "Your previous request to create the named bucket succeeded and you already own it.", + http.StatusConflict) + + // request for ACL. + // Since Minio server doesn't support ACL's the request is expected to fail with "NotImplemented" error message. + request, err = newTestSignedRequestV2("PUT", s.endPoint+"/"+bucketName+"?acl", + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + response, err = client.Do(request) + c.Assert(err, IsNil) + verifyError(c, response, "NotImplemented", "A header you provided implies functionality that is not implemented", http.StatusNotImplemented) +} + +func (s *TestSuiteCommonV2) TestGetObjectLarge10MiB(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // form HTTP reqest to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create the bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + var buffer bytes.Buffer + line := `1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,123"` + // Create 10MiB content where each line contains 1024 characters. + for i := 0; i < 10*1024; i++ { + buffer.WriteString(fmt.Sprintf("[%05d] %s\n", i, line)) + } + putContent := buffer.String() + + buf := bytes.NewReader([]byte(putContent)) + + objectName := "test-big-object" + // create HTTP request for object upload. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buf.Len()), buf, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // Assert the status code to verify successful upload. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // prepare HTTP requests to download the object. + request, err = newTestSignedRequestV2("GET", getPutObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request to download the object. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + // extract the content from response body. + getContent, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + + // Compare putContent and getContent. + c.Assert(string(getContent), Equals, putContent) +} + +// TestGetObjectLarge11MiB - Tests validate fetching of an object of size 11MB. +func (s *TestSuiteCommonV2) TestGetObjectLarge11MiB(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + var buffer bytes.Buffer + line := `1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,123` + // Create 11MiB content where each line contains 1024 characters. + for i := 0; i < 11*1024; i++ { + buffer.WriteString(fmt.Sprintf("[%05d] %s\n", i, line)) + } + putMD5 := sumMD5(buffer.Bytes()) + + objectName := "test-11Mb-object" + // Put object + buf := bytes.NewReader(buffer.Bytes()) + // create HTTP request foe object upload. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buf.Len()), buf, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request for object upload. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // create HTTP request to download the object. + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + // fetch the content from response body. + getContent, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + + // Get md5Sum of the response content. + getMD5 := sumMD5(getContent) + + // Compare putContent and getContent. + c.Assert(hex.EncodeToString(putMD5), Equals, hex.EncodeToString(getMD5)) +} + +// TestGetPartialObjectMisAligned - tests get object partially mis-aligned. +// create a large buffer of mis-aligned data and upload it. +// then make partial range requests to while fetching it back and assert the response content. +func (s *TestSuiteCommonV2) TestGetPartialObjectMisAligned(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create the bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + var buffer bytes.Buffer + line := `1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,123` + + rand.Seed(time.Now().UTC().UnixNano()) + // Create a misalgined data. + for i := 0; i < 13*rand.Intn(1<<16); i++ { + buffer.WriteString(fmt.Sprintf("[%05d] %s\n", i, line[:rand.Intn(1<<8)])) + } + putContent := buffer.String() + buf := bytes.NewReader([]byte(putContent)) + + objectName := "test-big-file" + // HTTP request to upload the object. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buf.Len()), buf, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request to upload the object. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // test Cases containing data to make partial range requests. + // also has expected response data. + var testCases = []struct { + byteRange string + expectedString string + }{ + // request for byte range 10-11. + // expecting the result to contain only putContent[10:12] bytes. + {"10-11", putContent[10:12]}, + // request for object data after the first byte. + {"1-", putContent[1:]}, + // request for object data after the first byte. + {"6-", putContent[6:]}, + // request for last 2 bytes of th object. + {"-2", putContent[len(putContent)-2:]}, + // request for last 7 bytes of the object. + {"-7", putContent[len(putContent)-7:]}, + } + for _, t := range testCases { + // HTTP request to download the object. + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // Get partial content based on the byte range set. + request.Header.Add("Range", "bytes="+t.byteRange) + + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // Since only part of the object is requested, expecting response status to be http.StatusPartialContent . + c.Assert(response.StatusCode, Equals, http.StatusPartialContent) + // parse the HTTP response body. + getContent, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + + // Compare putContent and getContent. + c.Assert(string(getContent), Equals, t.expectedString) + } +} + +// TestGetPartialObjectLarge11MiB - Test validates partial content request for a 11MiB object. +func (s *TestSuiteCommonV2) TestGetPartialObjectLarge11MiB(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create the bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + var buffer bytes.Buffer + line := `234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,123` + // Create 11MiB content where each line contains 1024 + // characters. + for i := 0; i < 11*1024; i++ { + buffer.WriteString(fmt.Sprintf("[%05d] %s\n", i, line)) + } + putContent := buffer.String() + + objectName := "test-large-11Mb-object" + + buf := bytes.NewReader([]byte(putContent)) + // HTTP request to upload the object. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buf.Len()), buf, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request to upload the object. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // HTTP request to download the object. + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // This range spans into first two blocks. + request.Header.Add("Range", "bytes=10485750-10485769") + + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // Since only part of the object is requested, expecting response status to be http.StatusPartialContent . + c.Assert(response.StatusCode, Equals, http.StatusPartialContent) + // read the downloaded content from the response body. + getContent, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + + // Compare putContent and getContent. + c.Assert(string(getContent), Equals, putContent[10485750:10485770]) +} + +// TestGetPartialObjectLarge11MiB - Test validates partial content request for a 10MiB object. +func (s *TestSuiteCommonV2) TestGetPartialObjectLarge10MiB(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + // expecting the error to be nil. + c.Assert(err, IsNil) + // expecting the HTTP response status code to 200 OK. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + var buffer bytes.Buffer + line := `1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,123` + // Create 10MiB content where each line contains 1024 characters. + for i := 0; i < 10*1024; i++ { + buffer.WriteString(fmt.Sprintf("[%05d] %s\n", i, line)) + } + + putContent := buffer.String() + buf := bytes.NewReader([]byte(putContent)) + + objectName := "test-big-10Mb-file" + // HTTP request to upload the object. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buf.Len()), buf, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request to upload the object. + response, err = client.Do(request) + c.Assert(err, IsNil) + // verify whether upload was successful. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // HTTP request to download the object. + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // Get partial content based on the byte range set. + request.Header.Add("Range", "bytes=2048-2058") + + client = http.Client{} + // execute the HTTP request to download the partila content. + response, err = client.Do(request) + c.Assert(err, IsNil) + // Since only part of the object is requested, expecting response status to be http.StatusPartialContent . + c.Assert(response.StatusCode, Equals, http.StatusPartialContent) + // read the downloaded content from the response body. + getContent, err := ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + + // Compare putContent and getContent. + c.Assert(string(getContent), Equals, putContent[2048:2059]) +} + +// TestGetObjectErrors - Tests validate error response for invalid object operations. +func (s *TestSuiteCommonV2) TestGetObjectErrors(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + objectName := "test-non-exitent-object" + // HTTP request to download the object. + // Since the specified object doesn't exist in the given bucket, + // expected to fail with error message "NoSuchKey" + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + verifyError(c, response, "NoSuchKey", "The specified key does not exist.", http.StatusNotFound) + + // request to download an object, but an invalid bucket name is set. + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, "/getobjecterrors-.", objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // expected to fail with "InvalidBucketName". + verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest) +} + +// TestGetObjectRangeErrors - Validate error response when object is fetched with incorrect byte range value. +func (s *TestSuiteCommonV2) TestGetObjectRangeErrors(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // content for the object to be uploaded. + buffer1 := bytes.NewReader([]byte("Hello World")) + + objectName := "test-object" + // HTTP request to upload the object. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request to upload the object. + response, err = client.Do(request) + c.Assert(err, IsNil) + // verify whether upload was successful. + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // HTTP request to download the object. + request, err = newTestSignedRequestV2("GET", getGetObjectURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + // Invalid byte range set. + request.Header.Add("Range", "bytes=-0") + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // expected to fail with "InvalidRange" error message. + verifyError(c, response, "InvalidRange", "The requested range is not satisfiable", http.StatusRequestedRangeNotSatisfiable) +} + +// TestObjectMultipartAbort - Test validates abortion of a multipart upload after uploading 2 parts. +func (s *TestSuiteCommonV2) TestObjectMultipartAbort(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + objectName := "test-multipart-object" + + // 1. Initiate 2 uploads for the same object + // 2. Upload 2 parts for the second upload + // 3. Abort the second upload. + // 4. Abort the first upload. + // This will test abort upload when there are more than one upload IDs + // and the case where there is only one upload ID. + + // construct HTTP request to initiate a NewMultipart upload. + request, err = newTestSignedRequestV2("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + // execute the HTTP request initiating the new multipart upload. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // parse the response body and obtain the new upload ID. + decoder := xml.NewDecoder(response.Body) + newResponse := &InitiateMultipartUploadResponse{} + + err = decoder.Decode(newResponse) + c.Assert(err, IsNil) + c.Assert(len(newResponse.UploadID) > 0, Equals, true) + + // construct HTTP request to initiate a NewMultipart upload. + request, err = newTestSignedRequestV2("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + // execute the HTTP request initiating the new multipart upload. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // parse the response body and obtain the new upload ID. + decoder = xml.NewDecoder(response.Body) + newResponse = &InitiateMultipartUploadResponse{} + + err = decoder.Decode(newResponse) + c.Assert(err, IsNil) + c.Assert(len(newResponse.UploadID) > 0, Equals, true) + // uploadID to be used for rest of the multipart operations on the object. + uploadID := newResponse.UploadID + + // content for the part to be uploaded. + buffer1 := bytes.NewReader([]byte("hello world")) + // HTTP request for the part to be uploaded. + request, err = newTestSignedRequestV2("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "1"), + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request to upload the first part. + response1, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response1.StatusCode, Equals, http.StatusOK) + + // content for the second part to be uploaded. + buffer2 := bytes.NewReader([]byte("hello world")) + // HTTP request for the second part to be uploaded. + request, err = newTestSignedRequestV2("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "2"), + int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request to upload the second part. + response2, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response2.StatusCode, Equals, http.StatusOK) + // HTTP request for aborting the multipart upload. + request, err = newTestSignedRequestV2("DELETE", getAbortMultipartUploadURL(s.endPoint, bucketName, objectName, uploadID), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request to abort the multipart upload. + response3, err := client.Do(request) + c.Assert(err, IsNil) + // expecting the response status code to be http.StatusNoContent. + // The assertion validates the success of Abort Multipart operation. + c.Assert(response3.StatusCode, Equals, http.StatusNoContent) +} + +// TestBucketMultipartList - Initiates a NewMultipart upload, uploads parts and validates listing of the parts. +func (s *TestSuiteCommonV2) TestBucketMultipartList(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), 0, + nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, 200) + + objectName := "test-multipart-object" + // construct HTTP request to initiate a NewMultipart upload. + request, err = newTestSignedRequestV2("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request initiating the new multipart upload. + response, err = client.Do(request) + c.Assert(err, IsNil) + // expecting the response status code to be http.StatusOK(200 OK) . + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // parse the response body and obtain the new upload ID. + decoder := xml.NewDecoder(response.Body) + newResponse := &InitiateMultipartUploadResponse{} + + err = decoder.Decode(newResponse) + c.Assert(err, IsNil) + c.Assert(len(newResponse.UploadID) > 0, Equals, true) + // uploadID to be used for rest of the multipart operations on the object. + uploadID := newResponse.UploadID + + // content for the part to be uploaded. + buffer1 := bytes.NewReader([]byte("hello world")) + // HTTP request for the part to be uploaded. + request, err = newTestSignedRequestV2("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "1"), + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request to upload the first part. + response1, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response1.StatusCode, Equals, http.StatusOK) + + // content for the second part to be uploaded. + buffer2 := bytes.NewReader([]byte("hello world")) + // HTTP request for the second part to be uploaded. + request, err = newTestSignedRequestV2("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "2"), + int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request to upload the second part. + response2, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response2.StatusCode, Equals, http.StatusOK) + + // HTTP request to ListMultipart Uploads. + request, err = newTestSignedRequestV2("GET", getListMultipartURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request. + response3, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response3.StatusCode, Equals, http.StatusOK) + + // The reason to duplicate this structure here is to verify if the + // unmarshalling works from a client perspective, specifically + // while unmarshalling time.Time type for 'Initiated' field. + // time.Time does not honor xml marshaler, it means that we need + // to encode/format it before giving it to xml marshalling. + + // This below check adds client side verification to see if its + // truly parseable. + + // listMultipartUploadsResponse - format for list multipart uploads response. + type listMultipartUploadsResponse struct { + XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListMultipartUploadsResult" json:"-"` + + Bucket string + KeyMarker string + UploadIDMarker string `xml:"UploadIdMarker"` + NextKeyMarker string + NextUploadIDMarker string `xml:"NextUploadIdMarker"` + EncodingType string + MaxUploads int + IsTruncated bool + // All the in progress multipart uploads. + Uploads []struct { + Key string + UploadID string `xml:"UploadId"` + Initiator Initiator + Owner Owner + StorageClass string + Initiated time.Time // Keep this native to be able to parse properly. + } + Prefix string + Delimiter string + CommonPrefixes []CommonPrefix + } + + // parse the response body. + decoder = xml.NewDecoder(response3.Body) + newResponse3 := &listMultipartUploadsResponse{} + err = decoder.Decode(newResponse3) + c.Assert(err, IsNil) + // Assert the bucket name in the response with the expected bucketName. + c.Assert(newResponse3.Bucket, Equals, bucketName) + // Assert the bucket name in the response with the expected bucketName. + c.Assert(newResponse3.IsTruncated, Equals, false) +} + +// TestValidateObjectMultipartUploadID - Test Initiates a new multipart upload and validates the uploadID. +func (s *TestSuiteCommonV2) TestValidateObjectMultipartUploadID(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, 200) + + objectName := "directory1/directory2/object" + // construct HTTP request to initiate a NewMultipart upload. + request, err = newTestSignedRequestV2("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request initiating the new multipart upload. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // parse the response body and obtain the new upload ID. + decoder := xml.NewDecoder(response.Body) + newResponse := &InitiateMultipartUploadResponse{} + err = decoder.Decode(newResponse) + // expecting the decoding error to be nil. + c.Assert(err, IsNil) + // Verifying for Upload ID value to be greater than 0. + c.Assert(len(newResponse.UploadID) > 0, Equals, true) +} + +// TestObjectMultipartListError - Initiates a NewMultipart upload, uploads parts and validates +// error response for an incorrect max-parts parameter . +func (s *TestSuiteCommonV2) TestObjectMultipartListError(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, 200) + + objectName := "test-multipart-object" + // construct HTTP request to initiate a NewMultipart upload. + request, err = newTestSignedRequestV2("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request initiating the new multipart upload. + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + // parse the response body and obtain the new upload ID. + decoder := xml.NewDecoder(response.Body) + newResponse := &InitiateMultipartUploadResponse{} + + err = decoder.Decode(newResponse) + c.Assert(err, IsNil) + c.Assert(len(newResponse.UploadID) > 0, Equals, true) + // uploadID to be used for rest of the multipart operations on the object. + uploadID := newResponse.UploadID + + // content for the part to be uploaded. + buffer1 := bytes.NewReader([]byte("hello world")) + // HTTP request for the part to be uploaded. + request, err = newTestSignedRequestV2("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "1"), + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request to upload the first part. + response1, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response1.StatusCode, Equals, http.StatusOK) + + // content for the second part to be uploaded. + buffer2 := bytes.NewReader([]byte("hello world")) + // HTTP request for the second part to be uploaded. + request, err = newTestSignedRequestV2("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "2"), + int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + // execute the HTTP request to upload the second part. + response2, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response2.StatusCode, Equals, http.StatusOK) + + // HTTP request to ListMultipart Uploads. + // max-keys is set to valid value of 1 + request, err = newTestSignedRequestV2("GET", getListMultipartURLWithParams(s.endPoint, bucketName, objectName, uploadID, "1"), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request. + response3, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response3.StatusCode, Equals, http.StatusOK) + + // HTTP request to ListMultipart Uploads. + // max-keys is set to invalid value of -2. + request, err = newTestSignedRequestV2("GET", getListMultipartURLWithParams(s.endPoint, bucketName, objectName, uploadID, "-2"), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // execute the HTTP request. + response4, err := client.Do(request) + c.Assert(err, IsNil) + // Since max-keys parameter in the ListMultipart request set to invalid value of -2, + // its expected to fail with error message "InvalidArgument". + verifyError(c, response4, "InvalidArgument", "Argument max-parts must be an integer between 0 and 2147483647", http.StatusBadRequest) +} + +// TestObjectValidMD5 - First uploads an object with a valid Content-Md5 header and verifies the status, +// then upload an object in a wrong Content-Md5 and validate the error response. +func (s *TestSuiteCommonV2) TestObjectValidMD5(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, 200) + + // Create a byte array of 5MB. + // content for the object to be uploaded. + data := bytes.Repeat([]byte("0123456789abcdef"), 5*1024*1024/16) + // calculate md5Sum of the data. + hasher := md5.New() + hasher.Write(data) + md5Sum := hasher.Sum(nil) + + buffer1 := bytes.NewReader(data) + objectName := "test-1-object" + // HTTP request for the object to be uploaded. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // set the Content-Md5 to be the hash to content. + request.Header.Set("Content-Md5", base64.StdEncoding.EncodeToString(md5Sum)) + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + // expecting a successful upload. + c.Assert(response.StatusCode, Equals, http.StatusOK) + objectName = "test-2-object" + buffer1 = bytes.NewReader(data) + // HTTP request for the object to be uploaded. + request, err = newTestSignedRequestV2("PUT", getPutObjectURL(s.endPoint, bucketName, objectName), + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // set Content-Md5 to invalid value. + request.Header.Set("Content-Md5", "kvLTlMrX9NpYDQlEIFlnDA==") + // expecting a failure during upload. + client = http.Client{} + response, err = client.Do(request) + c.Assert(err, IsNil) + // Since Content-Md5 header was wrong, expecting to fail with "SignatureDoesNotMatch" error. + verifyError(c, response, "SignatureDoesNotMatch", "The request signature we calculated does not match the signature you provided. Check your key and signing method.", http.StatusForbidden) +} + +// TestObjectMultipart - Initiates a NewMultipart upload, uploads 2 parts, +// completes the multipart upload and validates the status of the operation. +func (s *TestSuiteCommonV2) TestObjectMultipart(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // HTTP request to create the bucket. + request, err := newTestSignedRequestV2("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, 200) + + objectName := "test-multipart-object" + // construct HTTP request to initiate a NewMultipart upload. + request, err = newTestSignedRequestV2("POST", getNewMultipartURL(s.endPoint, bucketName, objectName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request initiating the new multipart upload. + response, err = client.Do(request) + c.Assert(err, IsNil) + // expecting the response status code to be http.StatusOK(200 OK). + c.Assert(response.StatusCode, Equals, http.StatusOK) + // parse the response body and obtain the new upload ID. + decoder := xml.NewDecoder(response.Body) + newResponse := &InitiateMultipartUploadResponse{} + + err = decoder.Decode(newResponse) + c.Assert(err, IsNil) + c.Assert(len(newResponse.UploadID) > 0, Equals, true) + // uploadID to be used for rest of the multipart operations on the object. + uploadID := newResponse.UploadID + + // content for the part to be uploaded. + // Create a byte array of 5MB. + data := bytes.Repeat([]byte("0123456789abcdef"), 5*1024*1024/16) + // calculate md5Sum of the data. + hasher := md5.New() + hasher.Write(data) + md5Sum := hasher.Sum(nil) + + buffer1 := bytes.NewReader(data) + // HTTP request for the part to be uploaded. + request, err = newTestSignedRequestV2("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "1"), + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) + // set the Content-Md5 header to the base64 encoding the md5Sum of the content. + request.Header.Set("Content-Md5", base64.StdEncoding.EncodeToString(md5Sum)) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request to upload the first part. + response1, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response1.StatusCode, Equals, http.StatusOK) + + // content for the second part to be uploaded. + // Create a byte array of 1 byte. + data = []byte("0") + + hasher = md5.New() + hasher.Write(data) + // calculate md5Sum of the data. + md5Sum = hasher.Sum(nil) + + buffer2 := bytes.NewReader(data) + // HTTP request for the second part to be uploaded. + request, err = newTestSignedRequestV2("PUT", getPartUploadURL(s.endPoint, bucketName, objectName, uploadID, "2"), + int64(buffer2.Len()), buffer2, s.accessKey, s.secretKey) + // set the Content-Md5 header to the base64 encoding the md5Sum of the content. + request.Header.Set("Content-Md5", base64.StdEncoding.EncodeToString(md5Sum)) + c.Assert(err, IsNil) + + client = http.Client{} + // execute the HTTP request to upload the second part. + response2, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response2.StatusCode, Equals, http.StatusOK) + + // Complete multipart upload + completeUploads := &completeMultipartUpload{ + Parts: []completePart{ + { + PartNumber: 1, + ETag: response1.Header.Get("ETag"), + }, + { + PartNumber: 2, + ETag: response2.Header.Get("ETag"), + }, + }, + } + + completeBytes, err := xml.Marshal(completeUploads) + c.Assert(err, IsNil) + // Indicating that all parts are uploaded and initiating completeMultipartUpload. + request, err = newTestSignedRequestV2("POST", getCompleteMultipartUploadURL(s.endPoint, bucketName, objectName, uploadID), + int64(len(completeBytes)), bytes.NewReader(completeBytes), s.accessKey, s.secretKey) + c.Assert(err, IsNil) + // Execute the complete multipart request. + response, err = client.Do(request) + c.Assert(err, IsNil) + // verify whether complete multipart was successful. + c.Assert(response.StatusCode, Equals, http.StatusOK) + +} diff --git a/cmd/signature-v2-utils.go b/cmd/signature-v2-utils.go new file mode 100644 index 000000000..13d9b8632 --- /dev/null +++ b/cmd/signature-v2-utils.go @@ -0,0 +1,56 @@ +/* + * Minio Cloud Storage, (C) 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "bytes" + "net/url" + "sort" + "strings" +) + +// Replaces any occurring '/' in string, into its encoded representation. +func percentEncodeSlash(s string) string { + return strings.Replace(s, "/", "%2F", -1) +} + +// queryEncode - encodes query values in their URL encoded form. In +// addition to the percent encoding performed by getURLEncodedName() used +// here, it also percent encodes '/' (forward slash) +func queryEncode(v url.Values) string { + if v == nil { + return "" + } + var buf bytes.Buffer + keys := make([]string, 0, len(v)) + for k := range v { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + vs := v[k] + prefix := percentEncodeSlash(getURLEncodedName(k)) + "=" + for _, v := range vs { + if buf.Len() > 0 { + buf.WriteByte('&') + } + buf.WriteString(prefix) + buf.WriteString(percentEncodeSlash(getURLEncodedName(v))) + } + } + return buf.String() +} diff --git a/cmd/signature-v2.go b/cmd/signature-v2.go new file mode 100644 index 000000000..3eae6e0d0 --- /dev/null +++ b/cmd/signature-v2.go @@ -0,0 +1,303 @@ +/* + * Minio Cloud Storage, (C) 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "bytes" + "crypto/hmac" + "crypto/sha1" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "sort" + "strconv" + "strings" + "time" +) + +// Signature and API related constants. +const ( + signV2Algorithm = "AWS" +) + +// TODO add post policy signature. + +// doesPresignV2SignatureMatch - Verify query headers with presigned signature +// - http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationQueryStringAuth +// returns ErrNone if matches. S3 errors otherwise. +func doesPresignV2SignatureMatch(r *http.Request) APIErrorCode { + // Access credentials. + cred := serverConfig.GetCredential() + + // Copy request + req := *r + + // Validate if we do have query params. + if req.URL.Query().Encode() == "" { + return ErrInvalidQueryParams + } + + // Validate if access key id same. + if req.URL.Query().Get("AWSAccessKeyId") != cred.AccessKeyID { + return ErrInvalidAccessKeyID + } + + // Parse expires param into its native form. + expired, err := strconv.ParseInt(req.URL.Query().Get("Expires"), 10, 64) + if err != nil { + errorIf(err, "Unable to parse expires query param") + return ErrMalformedExpires + } + + // Validate if the request has already expired. + if expired < time.Now().UTC().Unix() { + return ErrExpiredPresignRequest + } + + // Get presigned string to sign. + stringToSign := preStringifyHTTPReq(req) + hm := hmac.New(sha1.New, []byte(cred.SecretAccessKey)) + hm.Write([]byte(stringToSign)) + + // Calculate signature and validate. + signature := base64.StdEncoding.EncodeToString(hm.Sum(nil)) + if req.URL.Query().Get("Signature") != signature { + return ErrSignatureDoesNotMatch + } + + // Success. + return ErrNone +} + +// Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature; +// Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) ); +// +// StringToSign = HTTP-Verb + "\n" + +// Content-Md5 + "\n" + +// Content-Type + "\n" + +// Date + "\n" + +// CanonicalizedProtocolHeaders + +// CanonicalizedResource; +// +// CanonicalizedResource = [ "/" + Bucket ] + +// + +// [ subresource, if present. For example "?acl", "?location", "?logging", or "?torrent"]; +// +// CanonicalizedProtocolHeaders = + +// doesSignV2Match - Verify authorization header with calculated header in accordance with +// - http://docs.aws.amazon.com/AmazonS3/latest/dev/auth-request-sig-v2.html +// returns true if matches, false otherwise. if error is not nil then it is always false +func doesSignV2Match(r *http.Request) APIErrorCode { + // Access credentials. + cred := serverConfig.GetCredential() + + // Copy request. + req := *r + + // Save authorization header. + v2Auth := req.Header.Get("Authorization") + if v2Auth == "" { + return ErrAuthHeaderEmpty + } + + // Add date if not present. + if date := req.Header.Get("Date"); date == "" { + if date = req.Header.Get("X-Amz-Date"); date == "" { + return ErrMissingDateHeader + } + } + + // Calculate HMAC for secretAccessKey. + stringToSign := stringifyHTTPReq(req) + hm := hmac.New(sha1.New, []byte(cred.SecretAccessKey)) + hm.Write([]byte(stringToSign)) + + // Prepare auth header. + authHeader := new(bytes.Buffer) + authHeader.WriteString(fmt.Sprintf("%s %s:", signV2Algorithm, cred.AccessKeyID)) + encoder := base64.NewEncoder(base64.StdEncoding, authHeader) + encoder.Write(hm.Sum(nil)) + encoder.Close() + + // Verify if signature match. + if authHeader.String() != v2Auth { + return ErrSignatureDoesNotMatch + } + + return ErrNone +} + +// From the Amazon docs: +// +// StringToSign = HTTP-Verb + "\n" + +// Content-Md5 + "\n" + +// Content-Type + "\n" + +// Expires + "\n" + +// CanonicalizedProtocolHeaders + +// CanonicalizedResource; +func preStringifyHTTPReq(req http.Request) string { + buf := new(bytes.Buffer) + // Write standard headers. + writePreSignV2Headers(buf, req) + // Write canonicalized protocol headers if any. + writeCanonicalizedHeaders(buf, req) + // Write canonicalized Query resources if any. + isPreSign := true + writeCanonicalizedResource(buf, req, isPreSign) + return buf.String() +} + +// writePreSignV2Headers - write preSign v2 required headers. +func writePreSignV2Headers(buf *bytes.Buffer, req http.Request) { + buf.WriteString(req.Method + "\n") + buf.WriteString(req.Header.Get("Content-Md5") + "\n") + buf.WriteString(req.Header.Get("Content-Type") + "\n") + buf.WriteString(req.Header.Get("Expires") + "\n") +} + +// From the Amazon docs: +// +// StringToSign = HTTP-Verb + "\n" + +// Content-Md5 + "\n" + +// Content-Type + "\n" + +// Date + "\n" + +// CanonicalizedProtocolHeaders + +// CanonicalizedResource; +func stringifyHTTPReq(req http.Request) string { + buf := new(bytes.Buffer) + // Write standard headers. + writeSignV2Headers(buf, req) + // Write canonicalized protocol headers if any. + writeCanonicalizedHeaders(buf, req) + // Write canonicalized Query resources if any. + isPreSign := false + writeCanonicalizedResource(buf, req, isPreSign) + return buf.String() +} + +// writeSignV2Headers - write signV2 required headers. +func writeSignV2Headers(buf *bytes.Buffer, req http.Request) { + buf.WriteString(req.Method + "\n") + buf.WriteString(req.Header.Get("Content-Md5") + "\n") + buf.WriteString(req.Header.Get("Content-Type") + "\n") + buf.WriteString(req.Header.Get("Date") + "\n") +} + +// writeCanonicalizedHeaders - write canonicalized headers. +func writeCanonicalizedHeaders(buf *bytes.Buffer, req http.Request) { + var protoHeaders []string + vals := make(map[string][]string) + for k, vv := range req.Header { + // All the AMZ headers should be lowercase + lk := strings.ToLower(k) + if strings.HasPrefix(lk, "x-amz") { + protoHeaders = append(protoHeaders, lk) + vals[lk] = vv + } + } + sort.Strings(protoHeaders) + for _, k := range protoHeaders { + buf.WriteString(k) + buf.WriteByte(':') + for idx, v := range vals[k] { + if idx > 0 { + buf.WriteByte(',') + } + if strings.Contains(v, "\n") { + // TODO: "Unfold" long headers that + // span multiple lines (as allowed by + // RFC 2616, section 4.2) by replacing + // the folding white-space (including + // new-line) by a single space. + buf.WriteString(v) + } else { + buf.WriteString(v) + } + } + buf.WriteByte('\n') + } +} + +// The following list is already sorted and should always be, otherwise we could +// have signature-related issues +var resourceList = []string{ + "acl", + "delete", + "location", + "logging", + "notification", + "partNumber", + "policy", + "requestPayment", + "torrent", + "uploadId", + "uploads", + "versionId", + "versioning", + "versions", + "website", +} + +// From the Amazon docs: +// +// CanonicalizedResource = [ "/" + Bucket ] + +// + +// [ sub-resource, if present. For example "?acl", "?location", "?logging", or "?torrent"]; +func writeCanonicalizedResource(buf *bytes.Buffer, req http.Request, isPreSign bool) { + // Save request URL. + requestURL := req.URL + // Get encoded URL path. + path := getURLEncodedName(requestURL.Path) + if isPreSign { + // Get encoded URL path. + if len(requestURL.Query()) > 0 { + // Keep the usual queries unescaped for string to sign. + query, _ := url.QueryUnescape(queryEncode(requestURL.Query())) + path = path + "?" + query + } + buf.WriteString(path) + return + } + buf.WriteString(path) + if requestURL.RawQuery != "" { + var n int + vals, _ := url.ParseQuery(requestURL.RawQuery) + // Verify if any sub resource queries are present, if yes + // canonicallize them. + for _, resource := range resourceList { + if vv, ok := vals[resource]; ok && len(vv) > 0 { + n++ + // First element + switch n { + case 1: + buf.WriteByte('?') + // The rest + default: + buf.WriteByte('&') + } + buf.WriteString(resource) + // Request parameters + if len(vv[0]) > 0 { + buf.WriteByte('=') + buf.WriteString(strings.Replace(url.QueryEscape(vv[0]), "+", "%20", -1)) + } + } + } + } +} diff --git a/cmd/signature-v2_test.go b/cmd/signature-v2_test.go new file mode 100644 index 000000000..8f5d7ffe5 --- /dev/null +++ b/cmd/signature-v2_test.go @@ -0,0 +1,159 @@ +package cmd + +import ( + "fmt" + "net/http" + "net/url" + "sort" + "testing" + "time" +) + +// Tests for 'func TestResourceListSorting(t *testing.T)'. +func TestResourceListSorting(t *testing.T) { + sortedResourceList := make([]string, len(resourceList)) + copy(sortedResourceList, resourceList) + sort.Strings(sortedResourceList) + for i := 0; i < len(resourceList); i++ { + if resourceList[i] != sortedResourceList[i] { + t.Errorf("Expected resourceList[%d] = \"%s\", resourceList is not correctly sorted.", i, sortedResourceList[i]) + break + } + } +} + +// Tests validate the query encoding. +func TestQueryEncode(t *testing.T) { + testCases := []struct { + // Input. + input url.Values + // Expected result. + result string + }{ + // % should be encoded as %25 + {url.Values{ + "key": []string{"thisisthe%url"}, + }, "key=thisisthe%25url"}, + // UTF-8 encoding. + {url.Values{ + "key": []string{"本語"}, + }, "key=%E6%9C%AC%E8%AA%9E"}, + // UTF-8 encoding with ASCII. + {url.Values{ + "key": []string{"本語.1"}, + }, "key=%E6%9C%AC%E8%AA%9E.1"}, + // Unusual ASCII characters. + {url.Values{ + "key": []string{">123"}, + }, "key=%3E123"}, + // Fragment path characters. + {url.Values{ + "key": []string{"myurl#link"}, + }, "key=myurl%23link"}, + // Space should be set to %20 not '+'. + {url.Values{ + "key": []string{"space in url"}, + }, "key=space%20in%20url"}, + // '+' shouldn't be treated as space. + {url.Values{ + "key": []string{"url+path"}, + }, "key=url%2Bpath"}, + // '/' shouldn't be treated as '/' should be percent coded. + {url.Values{ + "key": []string{"url/+path"}, + }, "key=url%2F%2Bpath"}, + // Values is empty and empty string. + {nil, ""}, + } + + // Tests generated values from url encoded name. + for i, testCase := range testCases { + result := queryEncode(testCase.input) + if testCase.result != result { + t.Errorf("Test %d: Expected queryEncoded result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result) + } + } +} + +func TestDoesPresignedV2SignatureMatch(t *testing.T) { + root, err := newTestConfig("us-east-1") + if err != nil { + t.Fatal("Unable to initialize test config.") + } + defer removeAll(root) + + now := time.Now().UTC() + + testCases := []struct { + queryParams map[string]string + headers map[string]string + expected APIErrorCode + }{ + // (0) Should error without a set URL query. + { + expected: ErrInvalidQueryParams, + }, + // (1) Should error on an invalid access key. + { + queryParams: map[string]string{ + "Expires": "60", + "Signature": "badsignature", + "AWSAccessKeyId": "Z7IXGOO6BZ0REAN1Q26I", + }, + expected: ErrInvalidAccessKeyID, + }, + // (2) Should error with malformed expires. + { + queryParams: map[string]string{ + "Expires": "60s", + "Signature": "badsignature", + "AWSAccessKeyId": serverConfig.GetCredential().AccessKeyID, + }, + expected: ErrMalformedExpires, + }, + // (3) Should give an expired request if it has expired. + { + queryParams: map[string]string{ + "Expires": "60", + "Signature": "badsignature", + "AWSAccessKeyId": serverConfig.GetCredential().AccessKeyID, + }, + expected: ErrExpiredPresignRequest, + }, + // (4) Should error when the signature does not match. + { + queryParams: map[string]string{ + "Expires": fmt.Sprintf("%d", now.Unix()+60), + "Signature": "badsignature", + "AWSAccessKeyId": serverConfig.GetCredential().AccessKeyID, + }, + expected: ErrSignatureDoesNotMatch, + }, + } + + // Run each test case individually. + for i, testCase := range testCases { + // Turn the map[string]string into map[string][]string, because Go. + query := url.Values{} + for key, value := range testCase.queryParams { + query.Set(key, value) + } + + // Create a request to use. + req, e := http.NewRequest(http.MethodGet, "http://host/a/b?"+query.Encode(), nil) + if e != nil { + t.Errorf("(%d) failed to create http.Request, got %v", i, e) + } + + // Do the same for the headers. + for key, value := range testCase.headers { + req.Header.Set(key, value) + } + + // Check if it matches! + err := doesPresignV2SignatureMatch(req) + if err != testCase.expected { + t.Errorf("(%d) expected to get %s, instead got %s", i, niceError(testCase.expected), niceError(err)) + } + } +} diff --git a/cmd/signature-v4-utils_test.go b/cmd/signature-v4-utils_test.go index 9b4be2513..03418a08d 100644 --- a/cmd/signature-v4-utils_test.go +++ b/cmd/signature-v4-utils_test.go @@ -22,7 +22,8 @@ import ( "time" ) -// TestSkipContentSha256Cksum - Test validate the logic which decides whether to skip checksum validation based on the request header. +// TestSkipContentSha256Cksum - Test validate the logic which decides whether +// to skip checksum validation based on the request header. func TestSkipContentSha256Cksum(t *testing.T) { testCases := []struct { inputHeaderKey string @@ -124,7 +125,7 @@ func TestGetURLEncodedName(t *testing.T) { for i, testCase := range testCases { result := getURLEncodedName(testCase.inputStr) if testCase.result != result { - t.Errorf("Test %d: Expected queryEncode result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result) + t.Errorf("Test %d: Expected URLEncoded result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result) } } } diff --git a/cmd/test-utils_test.go b/cmd/test-utils_test.go index 4156e3f25..ad658bb6f 100644 --- a/cmd/test-utils_test.go +++ b/cmd/test-utils_test.go @@ -18,6 +18,8 @@ package cmd import ( "bytes" + "crypto/hmac" + "crypto/sha1" "encoding/base64" "encoding/hex" "encoding/json" @@ -31,14 +33,12 @@ import ( "net/url" "os" "reflect" - "regexp" "sort" "strconv" "strings" "sync" "testing" "time" - "unicode/utf8" router "github.com/gorilla/mux" ) @@ -476,8 +476,76 @@ func newTestStreamingSignedRequest(method, urlStr string, contentLength, chunkSi return req, nil } +// preSignV2 - presign the request in following style. +// https://${S3_BUCKET}.s3.amazonaws.com/${S3_OBJECT}?AWSAccessKeyId=${S3_ACCESS_KEY}&Expires=${TIMESTAMP}&Signature=${SIGNATURE}. +func preSignV2(req *http.Request, accessKeyID, secretAccessKey string, expires int64) error { + // Presign is not needed for anonymous credentials. + if accessKeyID == "" || secretAccessKey == "" { + return errors.New("Presign cannot be generated without access and secret keys") + } + + d := time.Now().UTC() + // Find epoch expires when the request will expire. + epochExpires := d.Unix() + expires + + // Add expires header if not present. + if expiresStr := req.Header.Get("Expires"); expiresStr == "" { + req.Header.Set("Expires", strconv.FormatInt(epochExpires, 10)) + } + + // Get presigned string to sign. + stringToSign := preStringifyHTTPReq(*req) + hm := hmac.New(sha1.New, []byte(secretAccessKey)) + hm.Write([]byte(stringToSign)) + + // Calculate signature. + signature := base64.StdEncoding.EncodeToString(hm.Sum(nil)) + + query := req.URL.Query() + // Handle specially for Google Cloud Storage. + query.Set("AWSAccessKeyId", accessKeyID) + // Fill in Expires for presigned query. + query.Set("Expires", strconv.FormatInt(epochExpires, 10)) + + // Encode query and save. + req.URL.RawQuery = queryEncode(query) + + // Save signature finally. + req.URL.RawQuery += "&Signature=" + getURLEncodedName(signature) + + // Success. + return nil +} + +// Sign given request using Signature V2. +func signRequestV2(req *http.Request, accessKey, secretKey string) error { + // Initial time. + d := time.Now().UTC() + + // Add date if not present. + if date := req.Header.Get("Date"); date == "" { + req.Header.Set("Date", d.Format(http.TimeFormat)) + } + + // Calculate HMAC for secretAccessKey. + stringToSign := stringifyHTTPReq(*req) + hm := hmac.New(sha1.New, []byte(secretKey)) + hm.Write([]byte(stringToSign)) + + // Prepare auth header. + authHeader := new(bytes.Buffer) + authHeader.WriteString(fmt.Sprintf("%s %s:", signV2Algorithm, accessKey)) + encoder := base64.NewEncoder(base64.StdEncoding, authHeader) + encoder.Write(hm.Sum(nil)) + encoder.Close() + + // Set Authorization header. + req.Header.Set("Authorization", authHeader.String()) + return nil +} + // Sign given request using Signature V4. -func signRequest(req *http.Request, accessKey, secretKey string) error { +func signRequestV4(req *http.Request, accessKey, secretKey string) error { // Get hashed payload. hashedPayload := req.Header.Get("x-amz-content-sha256") if hashedPayload == "" { @@ -611,9 +679,9 @@ func newTestRequest(method, urlStr string, contentLength int64, body io.ReadSeek case body == nil: hashedPayload = hex.EncodeToString(sum256([]byte{})) default: - payloadBytes, e := ioutil.ReadAll(body) - if e != nil { - return nil, e + payloadBytes, err := ioutil.ReadAll(body) + if err != nil { + return nil, err } hashedPayload = hex.EncodeToString(sum256(payloadBytes)) md5Base64 := base64.StdEncoding.EncodeToString(sumMD5(payloadBytes)) @@ -635,8 +703,50 @@ func newTestRequest(method, urlStr string, contentLength int64, body io.ReadSeek return req, nil } +func newTestSignedRequestV2ContentType(method, urlStr string, contentLength int64, body io.ReadSeeker, accessKey, secretKey, contentType string) (*http.Request, error) { + req, err := newTestRequest(method, urlStr, contentLength, body) + if err != nil { + return nil, err + } + req.Header.Del("x-amz-content-sha256") + req.Header.Set("Content-Type", contentType) + + // Anonymous request return quickly. + if accessKey == "" || secretKey == "" { + return req, nil + } + + err = signRequestV2(req, accessKey, secretKey) + if err != nil { + return nil, err + } + + return req, nil +} + +// Returns new HTTP request object signed with signature v2. +func newTestSignedRequestV2(method, urlStr string, contentLength int64, body io.ReadSeeker, accessKey, secretKey string) (*http.Request, error) { + req, err := newTestRequest(method, urlStr, contentLength, body) + if err != nil { + return nil, err + } + req.Header.Del("x-amz-content-sha256") + + // Anonymous request return quickly. + if accessKey == "" || secretKey == "" { + return req, nil + } + + err = signRequestV2(req, accessKey, secretKey) + if err != nil { + return nil, err + } + + return req, nil +} + // Returns new HTTP request object signed with signature v4. -func newTestSignedRequest(method, urlStr string, contentLength int64, body io.ReadSeeker, accessKey, secretKey string) (*http.Request, error) { +func newTestSignedRequestV4(method, urlStr string, contentLength int64, body io.ReadSeeker, accessKey, secretKey string) (*http.Request, error) { req, err := newTestRequest(method, urlStr, contentLength, body) if err != nil { return nil, err @@ -647,7 +757,7 @@ func newTestSignedRequest(method, urlStr string, contentLength int64, body io.Re return req, nil } - err = signRequest(req, accessKey, secretKey) + err = signRequestV4(req, accessKey, secretKey) if err != nil { return nil, err } @@ -842,71 +952,6 @@ func (t *EOFWriter) Write(p []byte) (n int, err error) { return } -// queryEncode - encodes query values in their URL encoded form. -func queryEncode(v url.Values) string { - if v == nil { - return "" - } - var buf bytes.Buffer - keys := make([]string, 0, len(v)) - for k := range v { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - vs := v[k] - prefix := urlEncodePath(k) + "=" - for _, v := range vs { - if buf.Len() > 0 { - buf.WriteByte('&') - } - buf.WriteString(prefix) - buf.WriteString(urlEncodePath(v)) - } - } - return buf.String() -} - -// urlEncodePath encode the strings from UTF-8 byte representations to HTML hex escape sequences -// -// This is necessary since regular url.Parse() and url.Encode() functions do not support UTF-8 -// non english characters cannot be parsed due to the nature in which url.Encode() is written -// -// This function on the other hand is a direct replacement for url.Encode() technique to support -// pretty much every UTF-8 character. -func urlEncodePath(pathName string) string { - // if object matches reserved string, no need to encode them - reservedNames := regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$") - if reservedNames.MatchString(pathName) { - return pathName - } - var encodedPathname string - for _, s := range pathName { - if 'A' <= s && s <= 'Z' || 'a' <= s && s <= 'z' || '0' <= s && s <= '9' { // §2.3 Unreserved characters (mark) - encodedPathname = encodedPathname + string(s) - continue - } - switch s { - case '-', '_', '.', '~', '/': // §2.3 Unreserved characters (mark) - encodedPathname = encodedPathname + string(s) - continue - default: - len := utf8.RuneLen(s) - if len < 0 { - // if utf8 cannot convert return the same string as is - return pathName - } - u := make([]byte, len) - utf8.EncodeRune(u, s) - for _, r := range u { - hex := hex.EncodeToString([]byte{r}) - encodedPathname = encodedPathname + "%" + strings.ToUpper(hex) - } - } - } - return encodedPathname -} - // construct URL for http requests for bucket operations. func makeTestTargetURL(endPoint, bucketName, objectName string, queryValues url.Values) string { urlStr := endPoint + "/" @@ -914,7 +959,7 @@ func makeTestTargetURL(endPoint, bucketName, objectName string, queryValues url. urlStr = urlStr + bucketName + "/" } if objectName != "" { - urlStr = urlStr + urlEncodePath(objectName) + urlStr = urlStr + getURLEncodedName(objectName) } if len(queryValues) > 0 { urlStr = urlStr + "?" + queryEncode(queryValues)