storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/object-handlers_test.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2016, 2017, 2018 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package cmd
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"crypto/md5"
    23  	"encoding/base64"
    24  	"encoding/hex"
    25  	"encoding/xml"
    26  	"fmt"
    27  	"io"
    28  	"runtime"
    29  	"strings"
    30  
    31  	"io/ioutil"
    32  	"net/http"
    33  	"net/http/httptest"
    34  	"net/url"
    35  	"strconv"
    36  	"sync"
    37  	"testing"
    38  
    39  	humanize "github.com/dustin/go-humanize"
    40  
    41  	xhttp "storj.io/minio/cmd/http"
    42  	"storj.io/minio/pkg/auth"
    43  	ioutilx "storj.io/minio/pkg/ioutil"
    44  )
    45  
    46  // Type to capture different modifications to API request to simulate failure cases.
    47  type Fault int
    48  
    49  const (
    50  	None Fault = iota
    51  	MissingContentLength
    52  	TooBigObject
    53  	TooBigDecodedLength
    54  	BadSignature
    55  	BadMD5
    56  	MissingUploadID
    57  )
    58  
    59  // Wrapper for calling HeadObject API handler tests for both Erasure multiple disks and FS single drive setup.
    60  func TestAPIHeadObjectHandler(t *testing.T) {
    61  	ExecObjectLayerAPITest(t, testAPIHeadObjectHandler, []string{"HeadObject"})
    62  }
    63  
    64  func testAPIHeadObjectHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
    65  	credentials auth.Credentials, t *testing.T) {
    66  	objectName := "test-object"
    67  	// set of byte data for PutObject.
    68  	// object has to be created before running tests for HeadObject.
    69  	// this is required even to assert the HeadObject data,
    70  	// since dataInserted === dataFetched back is a primary criteria for any object storage this assertion is critical.
    71  	bytesData := []struct {
    72  		byteData []byte
    73  	}{
    74  		{generateBytesData(6 * humanize.MiByte)},
    75  	}
    76  	// set of inputs for uploading the objects before tests for downloading is done.
    77  	putObjectInputs := []struct {
    78  		bucketName    string
    79  		objectName    string
    80  		contentLength int64
    81  		textData      []byte
    82  		metaData      map[string]string
    83  	}{
    84  		{bucketName, objectName, int64(len(bytesData[0].byteData)), bytesData[0].byteData, make(map[string]string)},
    85  	}
    86  	// iterate through the above set of inputs and upload the object.
    87  	for i, input := range putObjectInputs {
    88  		// uploading the object.
    89  		_, err := obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewReader(input.textData), input.contentLength, input.metaData[""], ""), ObjectOptions{UserDefined: input.metaData})
    90  		// if object upload fails stop the test.
    91  		if err != nil {
    92  			t.Fatalf("Put Object case %d:  Error uploading object: <ERROR> %v", i+1, err)
    93  		}
    94  	}
    95  
    96  	// test cases with inputs and expected result for HeadObject.
    97  	testCases := []struct {
    98  		bucketName string
    99  		objectName string
   100  		accessKey  string
   101  		secretKey  string
   102  		// expected output.
   103  		expectedRespStatus int // expected response status body.
   104  	}{
   105  		// Test case - 1.
   106  		// Fetching stat info of object and validating it.
   107  		{
   108  			bucketName:         bucketName,
   109  			objectName:         objectName,
   110  			accessKey:          credentials.AccessKey,
   111  			secretKey:          credentials.SecretKey,
   112  			expectedRespStatus: http.StatusOK,
   113  		},
   114  		// Test case - 2.
   115  		// Case with non-existent object name.
   116  		{
   117  			bucketName:         bucketName,
   118  			objectName:         "abcd",
   119  			accessKey:          credentials.AccessKey,
   120  			secretKey:          credentials.SecretKey,
   121  			expectedRespStatus: http.StatusNotFound,
   122  		},
   123  		// Test case - 3.
   124  		// Test case to induce a signature mismatch.
   125  		// Using invalid accessID.
   126  		{
   127  			bucketName:         bucketName,
   128  			objectName:         objectName,
   129  			accessKey:          "Invalid-AccessID",
   130  			secretKey:          credentials.SecretKey,
   131  			expectedRespStatus: http.StatusForbidden,
   132  		},
   133  	}
   134  
   135  	// Iterating over the cases, fetching the object validating the response.
   136  	for i, testCase := range testCases {
   137  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   138  		rec := httptest.NewRecorder()
   139  		// construct HTTP request for Get Object end point.
   140  		req, err := newTestSignedRequestV4(http.MethodHead, getHeadObjectURL("", testCase.bucketName, testCase.objectName),
   141  			0, nil, testCase.accessKey, testCase.secretKey, nil)
   142  		if err != nil {
   143  			t.Fatalf("Test %d: %s: Failed to create HTTP request for Head Object: <ERROR> %v", i+1, instanceType, err)
   144  		}
   145  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
   146  		// Call the ServeHTTP to execute the handler,`func (api ObjectAPIHandlers) GetObjectHandler`  handles the request.
   147  		apiRouter.ServeHTTP(rec, req)
   148  
   149  		// Assert the response code with the expected status.
   150  		if rec.Code != testCase.expectedRespStatus {
   151  			t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code)
   152  		}
   153  
   154  		// Verify response of the V2 signed HTTP request.
   155  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   156  		recV2 := httptest.NewRecorder()
   157  		// construct HTTP request for Head Object endpoint.
   158  		reqV2, err := newTestSignedRequestV2(http.MethodHead, getHeadObjectURL("", testCase.bucketName, testCase.objectName),
   159  			0, nil, testCase.accessKey, testCase.secretKey, nil)
   160  
   161  		if err != nil {
   162  			t.Fatalf("Test %d: %s: Failed to create HTTP request for Head Object: <ERROR> %v", i+1, instanceType, err)
   163  		}
   164  
   165  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
   166  		// Call the ServeHTTP to execute the handler.
   167  		apiRouter.ServeHTTP(recV2, reqV2)
   168  		if recV2.Code != testCase.expectedRespStatus {
   169  			t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code)
   170  		}
   171  	}
   172  
   173  	// Test for Anonymous/unsigned http request.
   174  	anonReq, err := newTestRequest(http.MethodHead, getHeadObjectURL("", bucketName, objectName), 0, nil)
   175  
   176  	if err != nil {
   177  		t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
   178  			instanceType, bucketName, objectName, err)
   179  	}
   180  
   181  	// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
   182  	// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
   183  	// unsigned request goes through and its validated again.
   184  	ExecObjectLayerAPIAnonTest(t, obj, "TestAPIHeadObjectHandler", bucketName, objectName, instanceType, apiRouter, anonReq, getAnonReadOnlyObjectPolicy(bucketName, objectName))
   185  
   186  	// HTTP request for testing when `objectLayer` is set to `nil`.
   187  	// There is no need to use an existing bucket and valid input for creating the request
   188  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
   189  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
   190  
   191  	nilBucket := "dummy-bucket"
   192  	nilObject := "dummy-object"
   193  	nilReq, err := newTestSignedRequestV4(http.MethodHead, getGetObjectURL("", nilBucket, nilObject),
   194  		0, nil, "", "", nil)
   195  
   196  	if err != nil {
   197  		t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
   198  	}
   199  	// execute the object layer set to `nil` test.
   200  	// `ExecObjectLayerAPINilTest` manages the operation.
   201  	ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
   202  }
   203  
   204  func TestAPIHeadObjectHandlerWithEncryption(t *testing.T) {
   205  	globalPolicySys = NewPolicySys()
   206  	defer func() { globalPolicySys = nil }()
   207  
   208  	defer DetectTestLeak(t)()
   209  	ExecObjectLayerAPITest(t, testAPIHeadObjectHandlerWithEncryption, []string{"NewMultipart", "PutObjectPart", "CompleteMultipart", "GetObject", "PutObject", "HeadObject"})
   210  }
   211  
   212  func testAPIHeadObjectHandlerWithEncryption(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
   213  	credentials auth.Credentials, t *testing.T) {
   214  
   215  	// Set SSL to on to do encryption tests
   216  	GlobalIsTLS = true
   217  	defer func() { GlobalIsTLS = false }()
   218  
   219  	var (
   220  		oneMiB        int64 = 1024 * 1024
   221  		key32Bytes          = generateBytesData(32 * humanize.Byte)
   222  		key32BytesMd5       = md5.Sum(key32Bytes)
   223  		metaWithSSEC        = map[string]string{
   224  			xhttp.AmzServerSideEncryptionCustomerAlgorithm: xhttp.AmzEncryptionAES,
   225  			xhttp.AmzServerSideEncryptionCustomerKey:       base64.StdEncoding.EncodeToString(key32Bytes),
   226  			xhttp.AmzServerSideEncryptionCustomerKeyMD5:    base64.StdEncoding.EncodeToString(key32BytesMd5[:]),
   227  		}
   228  		mapCopy = func(m map[string]string) map[string]string {
   229  			r := make(map[string]string, len(m))
   230  			for k, v := range m {
   231  				r[k] = v
   232  			}
   233  			return r
   234  		}
   235  	)
   236  
   237  	type ObjectInput struct {
   238  		objectName  string
   239  		partLengths []int64
   240  
   241  		metaData map[string]string
   242  	}
   243  
   244  	objectLength := func(oi ObjectInput) (sum int64) {
   245  		for _, l := range oi.partLengths {
   246  			sum += l
   247  		}
   248  		return
   249  	}
   250  
   251  	// set of inputs for uploading the objects before tests for
   252  	// downloading is done. Data bytes are from DummyDataGen.
   253  	objectInputs := []ObjectInput{
   254  		// Unencrypted objects
   255  		{"nothing", []int64{0}, nil},
   256  		{"small-1", []int64{509}, nil},
   257  
   258  		{"mp-1", []int64{5 * oneMiB, 1}, nil},
   259  		{"mp-2", []int64{5487701, 5487799, 3}, nil},
   260  
   261  		// Encrypted object
   262  		{"enc-nothing", []int64{0}, mapCopy(metaWithSSEC)},
   263  		{"enc-small-1", []int64{509}, mapCopy(metaWithSSEC)},
   264  
   265  		{"enc-mp-1", []int64{5 * oneMiB, 1}, mapCopy(metaWithSSEC)},
   266  		{"enc-mp-2", []int64{5487701, 5487799, 3}, mapCopy(metaWithSSEC)},
   267  	}
   268  
   269  	// iterate through the above set of inputs and upload the object.
   270  	for _, input := range objectInputs {
   271  		uploadTestObject(t, apiRouter, credentials, bucketName, input.objectName, input.partLengths, input.metaData, false)
   272  	}
   273  
   274  	for i, input := range objectInputs {
   275  		// initialize HTTP NewRecorder, this records any
   276  		// mutations to response writer inside the handler.
   277  		rec := httptest.NewRecorder()
   278  		// construct HTTP request for HEAD object.
   279  		req, err := newTestSignedRequestV4(http.MethodHead, getHeadObjectURL("", bucketName, input.objectName),
   280  			0, nil, credentials.AccessKey, credentials.SecretKey, nil)
   281  		if err != nil {
   282  			t.Fatalf("Test %d: %s: Failed to create HTTP request for Head Object: <ERROR> %v", i+1, instanceType, err)
   283  		}
   284  		// Since `apiRouter` satisfies `http.Handler` it has a
   285  		// ServeHTTP to execute the logic of the handler.
   286  		apiRouter.ServeHTTP(rec, req)
   287  
   288  		isEnc := false
   289  		expected := 200
   290  		if strings.HasPrefix(input.objectName, "enc-") {
   291  			isEnc = true
   292  			expected = 400
   293  		}
   294  		if rec.Code != expected {
   295  			t.Errorf("Test %d: expected code %d but got %d for object %s", i+1, expected, rec.Code, input.objectName)
   296  		}
   297  
   298  		contentLength := rec.Header().Get("Content-Length")
   299  		if isEnc {
   300  			// initialize HTTP NewRecorder, this records any
   301  			// mutations to response writer inside the handler.
   302  			rec := httptest.NewRecorder()
   303  			// construct HTTP request for HEAD object.
   304  			req, err := newTestSignedRequestV4(http.MethodHead, getHeadObjectURL("", bucketName, input.objectName),
   305  				0, nil, credentials.AccessKey, credentials.SecretKey, input.metaData)
   306  			if err != nil {
   307  				t.Fatalf("Test %d: %s: Failed to create HTTP request for Head Object: <ERROR> %v", i+1, instanceType, err)
   308  			}
   309  			// Since `apiRouter` satisfies `http.Handler` it has a
   310  			// ServeHTTP to execute the logic of the handler.
   311  			apiRouter.ServeHTTP(rec, req)
   312  
   313  			if rec.Code != 200 {
   314  				t.Errorf("Test %d: Did not receive a 200 response: %d", i+1, rec.Code)
   315  			}
   316  			contentLength = rec.Header().Get("Content-Length")
   317  		}
   318  
   319  		if contentLength != fmt.Sprintf("%d", objectLength(input)) {
   320  			t.Errorf("Test %d: Content length is mismatching: got %s (expected: %d)", i+1, contentLength, objectLength(input))
   321  		}
   322  	}
   323  }
   324  
   325  // Wrapper for calling GetObject API handler tests for both Erasure multiple disks and FS single drive setup.
   326  func TestAPIGetObjectHandler(t *testing.T) {
   327  	globalPolicySys = NewPolicySys()
   328  	defer func() { globalPolicySys = nil }()
   329  
   330  	defer DetectTestLeak(t)()
   331  	ExecExtendedObjectLayerAPITest(t, testAPIGetObjectHandler, []string{"GetObject"})
   332  }
   333  
   334  func testAPIGetObjectHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
   335  	credentials auth.Credentials, t *testing.T) {
   336  
   337  	objectName := "test-object"
   338  	// set of byte data for PutObject.
   339  	// object has to be created before running tests for GetObject.
   340  	// this is required even to assert the GetObject data,
   341  	// since dataInserted === dataFetched back is a primary criteria for any object storage this assertion is critical.
   342  	bytesData := []struct {
   343  		byteData []byte
   344  	}{
   345  		{generateBytesData(6 * humanize.MiByte)},
   346  	}
   347  	// set of inputs for uploading the objects before tests for downloading is done.
   348  	putObjectInputs := []struct {
   349  		bucketName    string
   350  		objectName    string
   351  		contentLength int64
   352  		textData      []byte
   353  		metaData      map[string]string
   354  	}{
   355  		// case - 1.
   356  		{bucketName, objectName, int64(len(bytesData[0].byteData)), bytesData[0].byteData, make(map[string]string)},
   357  	}
   358  	// iterate through the above set of inputs and upload the object.
   359  	for i, input := range putObjectInputs {
   360  		// uploading the object.
   361  		_, err := obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewReader(input.textData), input.contentLength, input.metaData[""], ""), ObjectOptions{UserDefined: input.metaData})
   362  		// if object upload fails stop the test.
   363  		if err != nil {
   364  			t.Fatalf("Put Object case %d:  Error uploading object: <ERROR> %v", i+1, err)
   365  		}
   366  	}
   367  
   368  	ctx := context.Background()
   369  
   370  	// test cases with inputs and expected result for GetObject.
   371  	testCases := []struct {
   372  		bucketName string
   373  		objectName string
   374  		byteRange  string // range of bytes to be fetched from GetObject.
   375  		accessKey  string
   376  		secretKey  string
   377  		// expected output.
   378  		expectedContent    []byte // expected response body.
   379  		expectedRespStatus int    // expected response status body.
   380  	}{
   381  		// Test case - 1.
   382  		// Fetching the entire object and validating its contents.
   383  		{
   384  			bucketName: bucketName,
   385  			objectName: objectName,
   386  			byteRange:  "",
   387  			accessKey:  credentials.AccessKey,
   388  			secretKey:  credentials.SecretKey,
   389  
   390  			expectedContent:    bytesData[0].byteData,
   391  			expectedRespStatus: http.StatusOK,
   392  		},
   393  		// Test case - 2.
   394  		// Case with non-existent object name.
   395  		{
   396  			bucketName: bucketName,
   397  			objectName: "abcd",
   398  			byteRange:  "",
   399  			accessKey:  credentials.AccessKey,
   400  			secretKey:  credentials.SecretKey,
   401  
   402  			expectedContent: EncodeResponse(getAPIErrorResponse(ctx,
   403  				GetAPIError(ErrNoSuchKey),
   404  				getGetObjectURL("", bucketName, "abcd"), "", "")),
   405  			expectedRespStatus: http.StatusNotFound,
   406  		},
   407  		// Test case - 3.
   408  		// Requesting from range 10-100.
   409  		{
   410  			bucketName: bucketName,
   411  			objectName: objectName,
   412  			byteRange:  "bytes=10-100",
   413  			accessKey:  credentials.AccessKey,
   414  			secretKey:  credentials.SecretKey,
   415  
   416  			expectedContent:    bytesData[0].byteData[10:101],
   417  			expectedRespStatus: http.StatusPartialContent,
   418  		},
   419  		// Test case - 4.
   420  		// Test case with invalid range.
   421  		{
   422  			bucketName: bucketName,
   423  			objectName: objectName,
   424  			byteRange:  "bytes=-0",
   425  			accessKey:  credentials.AccessKey,
   426  			secretKey:  credentials.SecretKey,
   427  
   428  			expectedContent: EncodeResponse(getAPIErrorResponse(ctx,
   429  				GetAPIError(ErrInvalidRange),
   430  				getGetObjectURL("", bucketName, objectName), "", "")),
   431  			expectedRespStatus: http.StatusRequestedRangeNotSatisfiable,
   432  		},
   433  		// Test case - 5.
   434  		// Test case with byte range exceeding the object size.
   435  		// Expected to read till end of the object.
   436  		{
   437  			bucketName: bucketName,
   438  			objectName: objectName,
   439  			byteRange:  "bytes=10-1000000000000000",
   440  			accessKey:  credentials.AccessKey,
   441  			secretKey:  credentials.SecretKey,
   442  
   443  			expectedContent:    bytesData[0].byteData[10:],
   444  			expectedRespStatus: http.StatusPartialContent,
   445  		},
   446  		// Test case - 6.
   447  		// Test case to induce a signature mismatch.
   448  		// Using invalid accessID.
   449  		{
   450  			bucketName: bucketName,
   451  			objectName: objectName,
   452  			byteRange:  "",
   453  			accessKey:  "Invalid-AccessID",
   454  			secretKey:  credentials.SecretKey,
   455  
   456  			expectedContent: EncodeResponse(getAPIErrorResponse(ctx,
   457  				GetAPIError(ErrInvalidAccessKeyID),
   458  				getGetObjectURL("", bucketName, objectName), "", "")),
   459  			expectedRespStatus: http.StatusForbidden,
   460  		},
   461  		// Test case - 7.
   462  		// Case with bad components in object name.
   463  		{
   464  			bucketName: bucketName,
   465  			objectName: "../../etc",
   466  			byteRange:  "",
   467  			accessKey:  credentials.AccessKey,
   468  			secretKey:  credentials.SecretKey,
   469  
   470  			expectedContent: EncodeResponse(getAPIErrorResponse(ctx,
   471  				GetAPIError(ErrInvalidObjectName),
   472  				getGetObjectURL("", bucketName, "../../etc"), "", "")),
   473  			expectedRespStatus: http.StatusBadRequest,
   474  		},
   475  		// Test case - 8.
   476  		// Case with strange components but returning error as not found.
   477  		{
   478  			bucketName: bucketName,
   479  			objectName: ". ./. ./etc",
   480  			byteRange:  "",
   481  			accessKey:  credentials.AccessKey,
   482  			secretKey:  credentials.SecretKey,
   483  
   484  			expectedContent: EncodeResponse(getAPIErrorResponse(ctx,
   485  				GetAPIError(ErrNoSuchKey),
   486  				SlashSeparator+bucketName+SlashSeparator+". ./. ./etc", "", "")),
   487  			expectedRespStatus: http.StatusNotFound,
   488  		},
   489  		// Test case - 9.
   490  		// Case with bad components in object name.
   491  		{
   492  			bucketName: bucketName,
   493  			objectName: ". ./../etc",
   494  			byteRange:  "",
   495  			accessKey:  credentials.AccessKey,
   496  			secretKey:  credentials.SecretKey,
   497  
   498  			expectedContent: EncodeResponse(getAPIErrorResponse(ctx,
   499  				GetAPIError(ErrInvalidObjectName),
   500  				SlashSeparator+bucketName+SlashSeparator+". ./../etc", "", "")),
   501  			expectedRespStatus: http.StatusBadRequest,
   502  		},
   503  		// Test case - 10.
   504  		// Case with proper components
   505  		{
   506  			bucketName: bucketName,
   507  			objectName: "etc/path/proper/.../etc",
   508  			byteRange:  "",
   509  			accessKey:  credentials.AccessKey,
   510  			secretKey:  credentials.SecretKey,
   511  
   512  			expectedContent: EncodeResponse(getAPIErrorResponse(ctx,
   513  				GetAPIError(ErrNoSuchKey),
   514  				getGetObjectURL("", bucketName, "etc/path/proper/.../etc"),
   515  				"", "")),
   516  			expectedRespStatus: http.StatusNotFound,
   517  		},
   518  	}
   519  
   520  	// Iterating over the cases, fetching the object validating the response.
   521  	for i, testCase := range testCases {
   522  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   523  		rec := httptest.NewRecorder()
   524  		// construct HTTP request for Get Object end point.
   525  		req, err := newTestSignedRequestV4(http.MethodGet, getGetObjectURL("", testCase.bucketName, testCase.objectName),
   526  			0, nil, testCase.accessKey, testCase.secretKey, nil)
   527  
   528  		if err != nil {
   529  			t.Fatalf("Test %d: Failed to create HTTP request for Get Object: <ERROR> %v", i+1, err)
   530  		}
   531  		if testCase.byteRange != "" {
   532  			req.Header.Set("Range", testCase.byteRange)
   533  		}
   534  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
   535  		// Call the ServeHTTP to execute the handler,`func (api ObjectAPIHandlers) GetObjectHandler`  handles the request.
   536  		apiRouter.ServeHTTP(rec, req)
   537  		// Assert the response code with the expected status.
   538  		if rec.Code != testCase.expectedRespStatus {
   539  			t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code)
   540  		}
   541  		// read the response body.
   542  		actualContent, err := ioutil.ReadAll(rec.Body)
   543  		if err != nil {
   544  			t.Fatalf("Test %d: %s: Failed reading response body: <ERROR> %v", i+1, instanceType, err)
   545  		}
   546  
   547  		if rec.Code == http.StatusOK || rec.Code == http.StatusPartialContent {
   548  			if !bytes.Equal(testCase.expectedContent, actualContent) {
   549  				t.Errorf("Test %d: %s: Object content differs from expected value %s, got %s", i+1, instanceType, testCase.expectedContent, string(actualContent))
   550  			}
   551  			continue
   552  		}
   553  
   554  		// Verify whether the bucket obtained object is same as the one created.
   555  		actualError := &APIErrorResponse{}
   556  		if err = xml.Unmarshal(actualContent, actualError); err != nil {
   557  			t.Fatalf("Test %d: %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
   558  		}
   559  
   560  		if actualError.BucketName != testCase.bucketName {
   561  			t.Fatalf("Test %d: %s: Unexpected bucket name, expected %s, got %s", i+1, instanceType, testCase.bucketName, actualError.BucketName)
   562  		}
   563  
   564  		if actualError.Key != testCase.objectName {
   565  			t.Fatalf("Test %d: %s: Unexpected object name, expected %s, got %s", i+1, instanceType, testCase.objectName, actualError.Key)
   566  		}
   567  
   568  		// Verify response of the V2 signed HTTP request.
   569  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
   570  		recV2 := httptest.NewRecorder()
   571  		// construct HTTP request for  GET Object endpoint.
   572  		reqV2, err := newTestSignedRequestV2(http.MethodGet, getGetObjectURL("", testCase.bucketName, testCase.objectName),
   573  			0, nil, testCase.accessKey, testCase.secretKey, nil)
   574  
   575  		if err != nil {
   576  			t.Fatalf("Test %d: %s: Failed to create HTTP request for GetObject: <ERROR> %v", i+1, instanceType, err)
   577  		}
   578  
   579  		if testCase.byteRange != "" {
   580  			reqV2.Header.Set("Range", testCase.byteRange)
   581  		}
   582  
   583  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
   584  		// Call the ServeHTTP to execute the handler.
   585  		apiRouter.ServeHTTP(recV2, reqV2)
   586  		if recV2.Code != testCase.expectedRespStatus {
   587  			t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code)
   588  		}
   589  
   590  		// read the response body.
   591  		actualContent, err = ioutil.ReadAll(recV2.Body)
   592  		if err != nil {
   593  			t.Fatalf("Test %d: %s: Failed to read response body: <ERROR> %v", i+1, instanceType, err)
   594  		}
   595  
   596  		if rec.Code == http.StatusOK || rec.Code == http.StatusPartialContent {
   597  			// Verify whether the bucket obtained object is same as the one created.
   598  			if !bytes.Equal(testCase.expectedContent, actualContent) {
   599  				t.Errorf("Test %d: %s: Object content differs from expected value.", i+1, instanceType)
   600  			}
   601  			continue
   602  		}
   603  
   604  		actualError = &APIErrorResponse{}
   605  		if err = xml.Unmarshal(actualContent, actualError); err != nil {
   606  			t.Fatalf("Test %d: %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
   607  		}
   608  
   609  		if actualError.BucketName != testCase.bucketName {
   610  			t.Fatalf("Test %d: %s: Unexpected bucket name, expected %s, got %s", i+1, instanceType, testCase.bucketName, actualError.BucketName)
   611  		}
   612  
   613  		if actualError.Key != testCase.objectName {
   614  			t.Fatalf("Test %d: %s: Unexpected object name, expected %s, got %s", i+1, instanceType, testCase.objectName, actualError.Key)
   615  		}
   616  	}
   617  
   618  	// Test for Anonymous/unsigned http request.
   619  	anonReq, err := newTestRequest(http.MethodGet, getGetObjectURL("", bucketName, objectName), 0, nil)
   620  
   621  	if err != nil {
   622  		t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
   623  			instanceType, bucketName, objectName, err)
   624  	}
   625  
   626  	// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
   627  	// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
   628  	// unsigned request goes through and its validated again.
   629  	ExecObjectLayerAPIAnonTest(t, obj, "TestAPIGetObjectHandler", bucketName, objectName, instanceType, apiRouter, anonReq, getAnonReadOnlyObjectPolicy(bucketName, objectName))
   630  
   631  	// HTTP request for testing when `objectLayer` is set to `nil`.
   632  	// There is no need to use an existing bucket and valid input for creating the request
   633  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
   634  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
   635  
   636  	nilBucket := "dummy-bucket"
   637  	nilObject := "dummy-object"
   638  	nilReq, err := newTestSignedRequestV4(http.MethodGet, getGetObjectURL("", nilBucket, nilObject),
   639  		0, nil, "", "", nil)
   640  
   641  	if err != nil {
   642  		t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
   643  	}
   644  	// execute the object layer set to `nil` test.
   645  	// `ExecObjectLayerAPINilTest` manages the operation.
   646  	ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
   647  }
   648  
   649  // Wrapper for calling GetObject API handler tests for both Erasure multiple disks and FS single drive setup.
   650  func TestAPIGetObjectWithMPHandler(t *testing.T) {
   651  	globalPolicySys = NewPolicySys()
   652  	defer func() { globalPolicySys = nil }()
   653  
   654  	defer DetectTestLeak(t)()
   655  	ExecExtendedObjectLayerAPITest(t, testAPIGetObjectWithMPHandler, []string{"NewMultipart", "PutObjectPart", "CompleteMultipart", "GetObject", "PutObject"})
   656  }
   657  
   658  func testAPIGetObjectWithMPHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
   659  	credentials auth.Credentials, t *testing.T) {
   660  
   661  	// Set SSL to on to do encryption tests
   662  	GlobalIsTLS = true
   663  	defer func() { GlobalIsTLS = false }()
   664  
   665  	var (
   666  		oneMiB        int64 = 1024 * 1024
   667  		key32Bytes          = generateBytesData(32 * humanize.Byte)
   668  		key32BytesMd5       = md5.Sum(key32Bytes)
   669  		metaWithSSEC        = map[string]string{
   670  			xhttp.AmzServerSideEncryptionCustomerAlgorithm: xhttp.AmzEncryptionAES,
   671  			xhttp.AmzServerSideEncryptionCustomerKey:       base64.StdEncoding.EncodeToString(key32Bytes),
   672  			xhttp.AmzServerSideEncryptionCustomerKeyMD5:    base64.StdEncoding.EncodeToString(key32BytesMd5[:]),
   673  		}
   674  		mapCopy = func(m map[string]string) map[string]string {
   675  			r := make(map[string]string, len(m))
   676  			for k, v := range m {
   677  				r[k] = v
   678  			}
   679  			return r
   680  		}
   681  	)
   682  
   683  	type ObjectInput struct {
   684  		objectName  string
   685  		partLengths []int64
   686  
   687  		metaData map[string]string
   688  	}
   689  
   690  	objectLength := func(oi ObjectInput) (sum int64) {
   691  		for _, l := range oi.partLengths {
   692  			sum += l
   693  		}
   694  		return
   695  	}
   696  
   697  	// set of inputs for uploading the objects before tests for
   698  	// downloading is done. Data bytes are from DummyDataGen.
   699  	objectInputs := []ObjectInput{
   700  		// // cases 0-3: small single part objects
   701  		{"nothing", []int64{0}, make(map[string]string)},
   702  		{"small-0", []int64{11}, make(map[string]string)},
   703  		{"small-1", []int64{509}, make(map[string]string)},
   704  		{"small-2", []int64{5 * oneMiB}, make(map[string]string)},
   705  		// // // cases 4-7: multipart part objects
   706  		{"mp-0", []int64{5 * oneMiB, 1}, make(map[string]string)},
   707  		{"mp-1", []int64{5*oneMiB + 1, 1}, make(map[string]string)},
   708  		{"mp-2", []int64{5487701, 5487799, 3}, make(map[string]string)},
   709  		{"mp-3", []int64{10499807, 10499963, 7}, make(map[string]string)},
   710  		// cases 8-11: small single part objects with encryption
   711  		{"enc-nothing", []int64{0}, mapCopy(metaWithSSEC)},
   712  		{"enc-small-0", []int64{11}, mapCopy(metaWithSSEC)},
   713  		{"enc-small-1", []int64{509}, mapCopy(metaWithSSEC)},
   714  		{"enc-small-2", []int64{5 * oneMiB}, mapCopy(metaWithSSEC)},
   715  		// cases 12-15: multipart part objects with encryption
   716  		{"enc-mp-0", []int64{5 * oneMiB, 1}, mapCopy(metaWithSSEC)},
   717  		{"enc-mp-1", []int64{5*oneMiB + 1, 1}, mapCopy(metaWithSSEC)},
   718  		{"enc-mp-2", []int64{5487701, 5487799, 3}, mapCopy(metaWithSSEC)},
   719  		{"enc-mp-3", []int64{10499807, 10499963, 7}, mapCopy(metaWithSSEC)},
   720  	}
   721  
   722  	// iterate through the above set of inputs and upload the object.
   723  	for _, input := range objectInputs {
   724  		uploadTestObject(t, apiRouter, credentials, bucketName, input.objectName, input.partLengths, input.metaData, false)
   725  	}
   726  
   727  	// function type for creating signed requests - used to repeat
   728  	// requests with V2 and V4 signing.
   729  	type testSignedReqFn func(method, urlStr string, contentLength int64,
   730  		body io.ReadSeeker, accessKey, secretKey string, metamap map[string]string) (*http.Request,
   731  		error)
   732  
   733  	mkGetReq := func(oi ObjectInput, byteRange string, i int, mkSignedReq testSignedReqFn) {
   734  		object := oi.objectName
   735  		rec := httptest.NewRecorder()
   736  		req, err := mkSignedReq(http.MethodGet, getGetObjectURL("", bucketName, object),
   737  			0, nil, credentials.AccessKey, credentials.SecretKey, oi.metaData)
   738  		if err != nil {
   739  			t.Fatalf("Object: %s Case %d ByteRange: %s: Failed to create HTTP request for Get Object: <ERROR> %v",
   740  				object, i+1, byteRange, err)
   741  		}
   742  
   743  		if byteRange != "" {
   744  			req.Header.Set("Range", byteRange)
   745  		}
   746  
   747  		apiRouter.ServeHTTP(rec, req)
   748  
   749  		// Check response code (we make only valid requests in
   750  		// this test)
   751  		if rec.Code != http.StatusPartialContent && rec.Code != http.StatusOK {
   752  			bd, err1 := ioutil.ReadAll(rec.Body)
   753  			t.Fatalf("%s Object: %s Case %d ByteRange: %s: Got response status `%d` and body: %s,%v",
   754  				instanceType, object, i+1, byteRange, rec.Code, string(bd), err1)
   755  		}
   756  
   757  		var off, length int64
   758  		var rs *HTTPRangeSpec
   759  		if byteRange != "" {
   760  			rs, err = parseRequestRangeSpec(byteRange)
   761  			if err != nil {
   762  				t.Fatalf("Object: %s Case %d ByteRange: %s: Unexpected err: %v", object, i+1, byteRange, err)
   763  			}
   764  		}
   765  		off, length, err = rs.GetOffsetLength(objectLength(oi))
   766  		if err != nil {
   767  			t.Fatalf("Object: %s Case %d ByteRange: %s: Unexpected err: %v", object, i+1, byteRange, err)
   768  		}
   769  
   770  		readers := []io.Reader{}
   771  		cumulativeSum := int64(0)
   772  		for _, p := range oi.partLengths {
   773  			readers = append(readers, NewDummyDataGen(p, cumulativeSum))
   774  			cumulativeSum += p
   775  		}
   776  		refReader := io.LimitReader(ioutilx.NewSkipReader(io.MultiReader(readers...), off), length)
   777  		if ok, msg := cmpReaders(refReader, rec.Body); !ok {
   778  			t.Fatalf("(%s) Object: %s Case %d ByteRange: %s --> data mismatch! (msg: %s)", instanceType, oi.objectName, i+1, byteRange, msg)
   779  		}
   780  	}
   781  
   782  	// Iterate over each uploaded object and do a bunch of get
   783  	// requests on them.
   784  	caseNumber := 0
   785  	signFns := []testSignedReqFn{newTestSignedRequestV2, newTestSignedRequestV4}
   786  	for _, oi := range objectInputs {
   787  		objLen := objectLength(oi)
   788  		for _, sf := range signFns {
   789  			// Read whole object
   790  			mkGetReq(oi, "", caseNumber, sf)
   791  			caseNumber++
   792  
   793  			// No range requests are possible if the
   794  			// object length is 0
   795  			if objLen == 0 {
   796  				continue
   797  			}
   798  
   799  			// Various ranges to query - all are valid!
   800  			rangeHdrs := []string{
   801  				// Read first byte of object
   802  				fmt.Sprintf("bytes=%d-%d", 0, 0),
   803  				// Read second byte of object
   804  				fmt.Sprintf("bytes=%d-%d", 1, 1),
   805  				// Read last byte of object
   806  				fmt.Sprintf("bytes=-%d", 1),
   807  				// Read all but first byte of object
   808  				"bytes=1-",
   809  				// Read first half of object
   810  				fmt.Sprintf("bytes=%d-%d", 0, objLen/2),
   811  				// Read last half of object
   812  				fmt.Sprintf("bytes=-%d", objLen/2),
   813  				// Read middle half of object
   814  				fmt.Sprintf("bytes=%d-%d", objLen/4, objLen*3/4),
   815  				// Read 100MiB of the object from the beginning
   816  				fmt.Sprintf("bytes=%d-%d", 0, 100*humanize.MiByte),
   817  				// Read 100MiB of the object from the end
   818  				fmt.Sprintf("bytes=-%d", 100*humanize.MiByte),
   819  			}
   820  			for _, rangeHdr := range rangeHdrs {
   821  				mkGetReq(oi, rangeHdr, caseNumber, sf)
   822  				caseNumber++
   823  			}
   824  		}
   825  
   826  	}
   827  
   828  	// HTTP request for testing when `objectLayer` is set to `nil`.
   829  	// There is no need to use an existing bucket and valid input for creating the request
   830  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
   831  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
   832  
   833  	nilBucket := "dummy-bucket"
   834  	nilObject := "dummy-object"
   835  	nilReq, err := newTestSignedRequestV4(http.MethodGet, getGetObjectURL("", nilBucket, nilObject),
   836  		0, nil, "", "", nil)
   837  
   838  	if err != nil {
   839  		t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
   840  	}
   841  	// execute the object layer set to `nil` test.
   842  	// `ExecObjectLayerAPINilTest` manages the operation.
   843  	ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
   844  
   845  }
   846  
   847  // Wrapper for calling GetObject API handler tests for both Erasure multiple disks and FS single drive setup.
   848  func TestAPIGetObjectWithPartNumberHandler(t *testing.T) {
   849  	globalPolicySys = NewPolicySys()
   850  	defer func() { globalPolicySys = nil }()
   851  
   852  	defer DetectTestLeak(t)()
   853  	ExecExtendedObjectLayerAPITest(t, testAPIGetObjectWithPartNumberHandler, []string{"NewMultipart", "PutObjectPart", "CompleteMultipart", "GetObject", "PutObject"})
   854  }
   855  
   856  func testAPIGetObjectWithPartNumberHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
   857  	credentials auth.Credentials, t *testing.T) {
   858  
   859  	// Set SSL to on to do encryption tests
   860  	GlobalIsTLS = true
   861  	defer func() { GlobalIsTLS = false }()
   862  
   863  	var (
   864  		oneMiB        int64 = 1024 * 1024
   865  		key32Bytes          = generateBytesData(32 * humanize.Byte)
   866  		key32BytesMd5       = md5.Sum(key32Bytes)
   867  		metaWithSSEC        = map[string]string{
   868  			xhttp.AmzServerSideEncryptionCustomerAlgorithm: xhttp.AmzEncryptionAES,
   869  			xhttp.AmzServerSideEncryptionCustomerKey:       base64.StdEncoding.EncodeToString(key32Bytes),
   870  			xhttp.AmzServerSideEncryptionCustomerKeyMD5:    base64.StdEncoding.EncodeToString(key32BytesMd5[:]),
   871  		}
   872  		mapCopy = func(m map[string]string) map[string]string {
   873  			r := make(map[string]string, len(m))
   874  			for k, v := range m {
   875  				r[k] = v
   876  			}
   877  			return r
   878  		}
   879  	)
   880  
   881  	type ObjectInput struct {
   882  		objectName  string
   883  		partLengths []int64
   884  
   885  		metaData map[string]string
   886  	}
   887  
   888  	// set of inputs for uploading the objects before tests for
   889  	// downloading is done. Data bytes are from DummyDataGen.
   890  	objectInputs := []ObjectInput{
   891  		// // cases 0-4: small single part objects
   892  		{"nothing", []int64{0}, make(map[string]string)},
   893  		{"1byte", []int64{1}, make(map[string]string)},
   894  		{"small-0", []int64{11}, make(map[string]string)},
   895  		{"small-1", []int64{509}, make(map[string]string)},
   896  		{"small-2", []int64{5 * oneMiB}, make(map[string]string)},
   897  		// // // cases 5-8: multipart part objects
   898  		{"mp-0", []int64{5 * oneMiB, 1}, make(map[string]string)},
   899  		{"mp-1", []int64{5*oneMiB + 1, 1}, make(map[string]string)},
   900  		{"mp-2", []int64{5487701, 5487799, 3}, make(map[string]string)},
   901  		{"mp-3", []int64{10499807, 10499963, 7}, make(map[string]string)},
   902  		// cases 9-12: small single part objects with encryption
   903  		{"enc-nothing", []int64{0}, mapCopy(metaWithSSEC)},
   904  		{"enc-small-0", []int64{11}, mapCopy(metaWithSSEC)},
   905  		{"enc-small-1", []int64{509}, mapCopy(metaWithSSEC)},
   906  		{"enc-small-2", []int64{5 * oneMiB}, mapCopy(metaWithSSEC)},
   907  		// cases 13-16: multipart part objects with encryption
   908  		{"enc-mp-0", []int64{5 * oneMiB, 1}, mapCopy(metaWithSSEC)},
   909  		{"enc-mp-1", []int64{5*oneMiB + 1, 1}, mapCopy(metaWithSSEC)},
   910  		{"enc-mp-2", []int64{5487701, 5487799, 3}, mapCopy(metaWithSSEC)},
   911  		{"enc-mp-3", []int64{10499807, 10499963, 7}, mapCopy(metaWithSSEC)},
   912  	}
   913  
   914  	// iterate through the above set of inputs and upload the object.
   915  	for _, input := range objectInputs {
   916  		uploadTestObject(t, apiRouter, credentials, bucketName, input.objectName, input.partLengths, input.metaData, false)
   917  	}
   918  
   919  	mkGetReqWithPartNumber := func(oindex int, oi ObjectInput, partNumber int) {
   920  		object := oi.objectName
   921  		rec := httptest.NewRecorder()
   922  
   923  		queries := url.Values{}
   924  		queries.Add("partNumber", strconv.Itoa(partNumber))
   925  		targetURL := makeTestTargetURL("", bucketName, object, queries)
   926  		req, err := newTestSignedRequestV4(http.MethodGet, targetURL,
   927  			0, nil, credentials.AccessKey, credentials.SecretKey, oi.metaData)
   928  		if err != nil {
   929  			t.Fatalf("Object: %s Object Index %d PartNumber: %d: Failed to create HTTP request for Get Object: <ERROR> %v",
   930  				object, oindex, partNumber, err)
   931  		}
   932  
   933  		apiRouter.ServeHTTP(rec, req)
   934  
   935  		// Check response code (we make only valid requests in this test)
   936  		if rec.Code != http.StatusPartialContent && rec.Code != http.StatusOK {
   937  			bd, err1 := ioutil.ReadAll(rec.Body)
   938  			t.Fatalf("%s Object: %s ObjectIndex %d PartNumber: %d: Got response status `%d` and body: %s,%v",
   939  				instanceType, object, oindex, partNumber, rec.Code, string(bd), err1)
   940  		}
   941  
   942  		oinfo, err := obj.GetObjectInfo(context.Background(), bucketName, object, ObjectOptions{})
   943  		if err != nil {
   944  			t.Fatalf("Object: %s Object Index %d: Unexpected err: %v", object, oindex, err)
   945  		}
   946  
   947  		rs := partNumberToRangeSpec(oinfo, partNumber)
   948  		size, err := oinfo.GetActualSize()
   949  		if err != nil {
   950  			t.Fatalf("Object: %s Object Index %d: Unexpected err: %v", object, oindex, err)
   951  		}
   952  
   953  		off, length, err := rs.GetOffsetLength(size)
   954  		if err != nil {
   955  			t.Fatalf("Object: %s Object Index %d: Unexpected err: %v", object, oindex, err)
   956  		}
   957  
   958  		readers := []io.Reader{}
   959  		cumulativeSum := int64(0)
   960  		for _, p := range oi.partLengths {
   961  			readers = append(readers, NewDummyDataGen(p, cumulativeSum))
   962  			cumulativeSum += p
   963  		}
   964  
   965  		refReader := io.LimitReader(ioutilx.NewSkipReader(io.MultiReader(readers...), off), length)
   966  		if ok, msg := cmpReaders(refReader, rec.Body); !ok {
   967  			t.Fatalf("(%s) Object: %s ObjectIndex %d PartNumber: %d --> data mismatch! (msg: %s)", instanceType, oi.objectName, oindex, partNumber, msg)
   968  		}
   969  	}
   970  
   971  	for idx, oi := range objectInputs {
   972  		for partNum := 1; partNum <= len(oi.partLengths); partNum++ {
   973  			mkGetReqWithPartNumber(idx, oi, partNum)
   974  		}
   975  	}
   976  }
   977  
   978  // Wrapper for calling PutObject API handler tests using streaming signature v4 for both Erasure multiple disks and FS single drive setup.
   979  func TestAPIPutObjectStreamSigV4Handler(t *testing.T) {
   980  	defer DetectTestLeak(t)()
   981  	ExecExtendedObjectLayerAPITest(t, testAPIPutObjectStreamSigV4Handler, []string{"PutObject"})
   982  }
   983  
   984  func testAPIPutObjectStreamSigV4Handler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
   985  	credentials auth.Credentials, t *testing.T) {
   986  
   987  	objectName := "test-object"
   988  	bytesDataLen := 65 * humanize.KiByte
   989  	bytesData := bytes.Repeat([]byte{'a'}, bytesDataLen)
   990  	oneKData := bytes.Repeat([]byte("a"), 1*humanize.KiByte)
   991  
   992  	var err error
   993  
   994  	type streamFault int
   995  	const (
   996  		None streamFault = iota
   997  		malformedEncoding
   998  		unexpectedEOF
   999  		signatureMismatch
  1000  		chunkDateMismatch
  1001  		tooBigDecodedLength
  1002  	)
  1003  
  1004  	// byte data for PutObject.
  1005  	// test cases with inputs and expected result for GetObject.
  1006  	testCases := []struct {
  1007  		bucketName string
  1008  		objectName string
  1009  		data       []byte
  1010  		dataLen    int
  1011  		chunkSize  int64
  1012  		// expected output.
  1013  		expectedContent    []byte // expected response body.
  1014  		expectedRespStatus int    // expected response status body.
  1015  		// Access keys
  1016  		accessKey        string
  1017  		secretKey        string
  1018  		shouldPass       bool
  1019  		removeAuthHeader bool
  1020  		fault            streamFault
  1021  		// Custom content encoding.
  1022  		contentEncoding string
  1023  	}{
  1024  		// Test case - 1.
  1025  		// Fetching the entire object and validating its contents.
  1026  		{
  1027  			bucketName:         bucketName,
  1028  			objectName:         objectName,
  1029  			data:               bytesData,
  1030  			dataLen:            len(bytesData),
  1031  			chunkSize:          64 * humanize.KiByte,
  1032  			expectedContent:    []byte{},
  1033  			expectedRespStatus: http.StatusOK,
  1034  			accessKey:          credentials.AccessKey,
  1035  			secretKey:          credentials.SecretKey,
  1036  			shouldPass:         true,
  1037  			fault:              None,
  1038  		},
  1039  		// Test case - 2
  1040  		// Small chunk size.
  1041  		{
  1042  			bucketName:         bucketName,
  1043  			objectName:         objectName,
  1044  			data:               bytesData,
  1045  			dataLen:            len(bytesData),
  1046  			chunkSize:          1 * humanize.KiByte,
  1047  			expectedContent:    []byte{},
  1048  			expectedRespStatus: http.StatusOK,
  1049  			accessKey:          credentials.AccessKey,
  1050  			secretKey:          credentials.SecretKey,
  1051  			shouldPass:         true,
  1052  			fault:              None,
  1053  		},
  1054  		// Test case - 3
  1055  		// Empty data
  1056  		{
  1057  			bucketName:         bucketName,
  1058  			objectName:         objectName,
  1059  			data:               []byte{},
  1060  			dataLen:            0,
  1061  			chunkSize:          64 * humanize.KiByte,
  1062  			expectedContent:    []byte{},
  1063  			expectedRespStatus: http.StatusOK,
  1064  			accessKey:          credentials.AccessKey,
  1065  			secretKey:          credentials.SecretKey,
  1066  			shouldPass:         true,
  1067  		},
  1068  		// Test case - 4
  1069  		// Invalid access key id.
  1070  		{
  1071  			bucketName:         bucketName,
  1072  			objectName:         objectName,
  1073  			data:               bytesData,
  1074  			dataLen:            len(bytesData),
  1075  			chunkSize:          64 * humanize.KiByte,
  1076  			expectedContent:    []byte{},
  1077  			expectedRespStatus: http.StatusForbidden,
  1078  			accessKey:          "",
  1079  			secretKey:          "",
  1080  			shouldPass:         false,
  1081  			fault:              None,
  1082  		},
  1083  		// Test case - 5
  1084  		// Wrong auth header returns as bad request.
  1085  		{
  1086  			bucketName:         bucketName,
  1087  			objectName:         objectName,
  1088  			data:               bytesData,
  1089  			dataLen:            len(bytesData),
  1090  			chunkSize:          64 * humanize.KiByte,
  1091  			expectedContent:    []byte{},
  1092  			expectedRespStatus: http.StatusBadRequest,
  1093  			accessKey:          credentials.AccessKey,
  1094  			secretKey:          credentials.SecretKey,
  1095  			shouldPass:         false,
  1096  			removeAuthHeader:   true,
  1097  			fault:              None,
  1098  		},
  1099  		// Test case - 6
  1100  		// Large chunk size.. also passes.
  1101  		{
  1102  			bucketName:         bucketName,
  1103  			objectName:         objectName,
  1104  			data:               bytesData,
  1105  			dataLen:            len(bytesData),
  1106  			chunkSize:          100 * humanize.KiByte,
  1107  			expectedContent:    []byte{},
  1108  			expectedRespStatus: http.StatusOK,
  1109  			accessKey:          credentials.AccessKey,
  1110  			secretKey:          credentials.SecretKey,
  1111  			shouldPass:         true,
  1112  			fault:              None,
  1113  		},
  1114  		// Test case - 7
  1115  		// Chunk with malformed encoding.
  1116  		{
  1117  			bucketName:         bucketName,
  1118  			objectName:         objectName,
  1119  			data:               oneKData,
  1120  			dataLen:            1024,
  1121  			chunkSize:          1024,
  1122  			expectedContent:    []byte{},
  1123  			expectedRespStatus: http.StatusInternalServerError,
  1124  			accessKey:          credentials.AccessKey,
  1125  			secretKey:          credentials.SecretKey,
  1126  			shouldPass:         false,
  1127  			fault:              malformedEncoding,
  1128  		},
  1129  		// Test case - 8
  1130  		// Chunk with shorter than advertised chunk data.
  1131  		{
  1132  			bucketName:         bucketName,
  1133  			objectName:         objectName,
  1134  			data:               oneKData,
  1135  			dataLen:            1024,
  1136  			chunkSize:          1024,
  1137  			expectedContent:    []byte{},
  1138  			expectedRespStatus: http.StatusBadRequest,
  1139  			accessKey:          credentials.AccessKey,
  1140  			secretKey:          credentials.SecretKey,
  1141  			shouldPass:         false,
  1142  			fault:              unexpectedEOF,
  1143  		},
  1144  		// Test case - 9
  1145  		// Chunk with first chunk data byte tampered.
  1146  		{
  1147  			bucketName:         bucketName,
  1148  			objectName:         objectName,
  1149  			data:               oneKData,
  1150  			dataLen:            1024,
  1151  			chunkSize:          1024,
  1152  			expectedContent:    []byte{},
  1153  			expectedRespStatus: http.StatusForbidden,
  1154  			accessKey:          credentials.AccessKey,
  1155  			secretKey:          credentials.SecretKey,
  1156  			shouldPass:         false,
  1157  			fault:              signatureMismatch,
  1158  		},
  1159  		// Test case - 10
  1160  		// Different date (timestamps) used in seed signature calculation
  1161  		// and chunks signature calculation.
  1162  		{
  1163  			bucketName:         bucketName,
  1164  			objectName:         objectName,
  1165  			data:               oneKData,
  1166  			dataLen:            1024,
  1167  			chunkSize:          1024,
  1168  			expectedContent:    []byte{},
  1169  			expectedRespStatus: http.StatusForbidden,
  1170  			accessKey:          credentials.AccessKey,
  1171  			secretKey:          credentials.SecretKey,
  1172  			shouldPass:         false,
  1173  			fault:              chunkDateMismatch,
  1174  		},
  1175  		// Test case - 11
  1176  		// Set x-amz-decoded-content-length to a value too big to hold in int64.
  1177  		{
  1178  			bucketName:         bucketName,
  1179  			objectName:         objectName,
  1180  			data:               oneKData,
  1181  			dataLen:            1024,
  1182  			chunkSize:          1024,
  1183  			expectedContent:    []byte{},
  1184  			expectedRespStatus: http.StatusInternalServerError,
  1185  			accessKey:          credentials.AccessKey,
  1186  			secretKey:          credentials.SecretKey,
  1187  			shouldPass:         false,
  1188  			fault:              tooBigDecodedLength,
  1189  		},
  1190  		// Test case - 12
  1191  		// Set custom content encoding should succeed and save the encoding properly.
  1192  		{
  1193  			bucketName:         bucketName,
  1194  			objectName:         objectName,
  1195  			data:               bytesData,
  1196  			dataLen:            len(bytesData),
  1197  			chunkSize:          100 * humanize.KiByte,
  1198  			expectedContent:    []byte{},
  1199  			expectedRespStatus: http.StatusOK,
  1200  			accessKey:          credentials.AccessKey,
  1201  			secretKey:          credentials.SecretKey,
  1202  			shouldPass:         true,
  1203  			contentEncoding:    "aws-chunked,gzip",
  1204  			fault:              None,
  1205  		},
  1206  	}
  1207  	// Iterating over the cases, fetching the object validating the response.
  1208  	for i, testCase := range testCases {
  1209  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
  1210  		rec := httptest.NewRecorder()
  1211  		// construct HTTP request for Put Object end point.
  1212  		var req *http.Request
  1213  		if testCase.fault == chunkDateMismatch {
  1214  			req, err = newTestStreamingSignedBadChunkDateRequest(http.MethodPut,
  1215  				getPutObjectURL("", testCase.bucketName, testCase.objectName),
  1216  				int64(testCase.dataLen), testCase.chunkSize, bytes.NewReader(testCase.data),
  1217  				testCase.accessKey, testCase.secretKey)
  1218  
  1219  		} else if testCase.contentEncoding == "" {
  1220  			req, err = newTestStreamingSignedRequest(http.MethodPut,
  1221  				getPutObjectURL("", testCase.bucketName, testCase.objectName),
  1222  				int64(testCase.dataLen), testCase.chunkSize, bytes.NewReader(testCase.data),
  1223  				testCase.accessKey, testCase.secretKey)
  1224  		} else if testCase.contentEncoding != "" {
  1225  			req, err = newTestStreamingSignedCustomEncodingRequest(http.MethodPut,
  1226  				getPutObjectURL("", testCase.bucketName, testCase.objectName),
  1227  				int64(testCase.dataLen), testCase.chunkSize, bytes.NewReader(testCase.data),
  1228  				testCase.accessKey, testCase.secretKey, testCase.contentEncoding)
  1229  		}
  1230  		if err != nil {
  1231  			t.Fatalf("Test %d: Failed to create HTTP request for Put Object: <ERROR> %v", i+1, err)
  1232  		}
  1233  		// Removes auth header if test case requires it.
  1234  		if testCase.removeAuthHeader {
  1235  			req.Header.Del("Authorization")
  1236  		}
  1237  		switch testCase.fault {
  1238  		case malformedEncoding:
  1239  			req, err = malformChunkSizeSigV4(req, testCase.chunkSize-1)
  1240  		case signatureMismatch:
  1241  			req, err = malformDataSigV4(req, 'z')
  1242  		case unexpectedEOF:
  1243  			req, err = truncateChunkByHalfSigv4(req)
  1244  		case tooBigDecodedLength:
  1245  			// Set decoded length to a large value out of int64 range to simulate parse failure.
  1246  			req.Header.Set("x-amz-decoded-content-length", "9999999999999999999999")
  1247  		}
  1248  
  1249  		if err != nil {
  1250  			t.Fatalf("Error injecting faults into the request: <ERROR> %v.", err)
  1251  		}
  1252  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
  1253  		// Call the ServeHTTP to execute the handler,`func (api ObjectAPIHandlers) GetObjectHandler`  handles the request.
  1254  		apiRouter.ServeHTTP(rec, req)
  1255  		// Assert the response code with the expected status.
  1256  		if rec.Code != testCase.expectedRespStatus {
  1257  			t.Errorf("Test %d %s: Expected the response status to be `%d`, but instead found `%d`: fault case %d",
  1258  				i+1, instanceType, testCase.expectedRespStatus, rec.Code, testCase.fault)
  1259  		}
  1260  		// read the response body.
  1261  		actualContent, err := ioutil.ReadAll(rec.Body)
  1262  		if err != nil {
  1263  			t.Fatalf("Test %d: %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
  1264  		}
  1265  		opts := ObjectOptions{}
  1266  		if testCase.shouldPass {
  1267  			// Verify whether the bucket obtained object is same as the one created.
  1268  			if !bytes.Equal(testCase.expectedContent, actualContent) {
  1269  				t.Errorf("Test %d: %s: Object content differs from expected value.: %s", i+1, instanceType, string(actualContent))
  1270  				continue
  1271  			}
  1272  			objInfo, err := obj.GetObjectInfo(context.Background(), testCase.bucketName, testCase.objectName, opts)
  1273  			if err != nil {
  1274  				t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
  1275  			}
  1276  			if objInfo.ContentEncoding == streamingContentEncoding {
  1277  				t.Fatalf("Test %d: %s: ContentEncoding is set to \"aws-chunked\" which is unexpected", i+1, instanceType)
  1278  			}
  1279  			expectedContentEncoding := trimAwsChunkedContentEncoding(testCase.contentEncoding)
  1280  			if expectedContentEncoding != objInfo.ContentEncoding {
  1281  				t.Fatalf("Test %d: %s: ContentEncoding is set to \"%s\" which is unexpected, expected \"%s\"", i+1, instanceType, objInfo.ContentEncoding, expectedContentEncoding)
  1282  			}
  1283  			buffer := new(bytes.Buffer)
  1284  			r, err := obj.GetObjectNInfo(context.Background(), testCase.bucketName, testCase.objectName, nil, nil, readLock, opts)
  1285  			if err != nil {
  1286  				t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
  1287  			}
  1288  			if _, err = io.Copy(buffer, r); err != nil {
  1289  				r.Close()
  1290  				t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
  1291  			}
  1292  			r.Close()
  1293  			if !bytes.Equal(testCase.data, buffer.Bytes()) {
  1294  				t.Errorf("Test %d: %s: Data Mismatch: Data fetched back from the uploaded object doesn't match the original one.", i+1, instanceType)
  1295  			}
  1296  		}
  1297  	}
  1298  }
  1299  
  1300  // Wrapper for calling PutObject API handler tests for both Erasure multiple disks and FS single drive setup.
  1301  func TestAPIPutObjectHandler(t *testing.T) {
  1302  	defer DetectTestLeak(t)()
  1303  	ExecExtendedObjectLayerAPITest(t, testAPIPutObjectHandler, []string{"PutObject"})
  1304  }
  1305  
  1306  func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
  1307  	credentials auth.Credentials, t *testing.T) {
  1308  
  1309  	var err error
  1310  	objectName := "test-object"
  1311  	opts := ObjectOptions{}
  1312  	// byte data for PutObject.
  1313  	bytesData := generateBytesData(6 * humanize.KiByte)
  1314  
  1315  	copySourceHeader := http.Header{}
  1316  	copySourceHeader.Set("X-Amz-Copy-Source", "somewhere")
  1317  	invalidMD5Header := http.Header{}
  1318  	invalidMD5Header.Set("Content-Md5", "42")
  1319  	inalidStorageClassHeader := http.Header{}
  1320  	inalidStorageClassHeader.Set(xhttp.AmzStorageClass, "INVALID")
  1321  
  1322  	addCustomHeaders := func(req *http.Request, customHeaders http.Header) {
  1323  		for k, values := range customHeaders {
  1324  			for _, value := range values {
  1325  				req.Header.Set(k, value)
  1326  			}
  1327  		}
  1328  	}
  1329  
  1330  	// test cases with inputs and expected result for GetObject.
  1331  	testCases := []struct {
  1332  		bucketName string
  1333  		objectName string
  1334  		headers    http.Header
  1335  		data       []byte
  1336  		dataLen    int
  1337  		accessKey  string
  1338  		secretKey  string
  1339  		fault      Fault
  1340  		// expected output.
  1341  		expectedRespStatus int // expected response status body.
  1342  	}{
  1343  		// Test case - 1.
  1344  		// Fetching the entire object and validating its contents.
  1345  		{
  1346  			bucketName: bucketName,
  1347  			objectName: objectName,
  1348  			data:       bytesData,
  1349  			dataLen:    len(bytesData),
  1350  			accessKey:  credentials.AccessKey,
  1351  			secretKey:  credentials.SecretKey,
  1352  
  1353  			expectedRespStatus: http.StatusOK,
  1354  		},
  1355  		// Test case - 2.
  1356  		// Test Case with invalid accessID.
  1357  		{
  1358  			bucketName: bucketName,
  1359  			objectName: objectName,
  1360  			data:       bytesData,
  1361  			dataLen:    len(bytesData),
  1362  			accessKey:  "Wrong-AcessID",
  1363  			secretKey:  credentials.SecretKey,
  1364  
  1365  			expectedRespStatus: http.StatusForbidden,
  1366  		},
  1367  		// Test case - 3.
  1368  		// Test Case with invalid header key X-Amz-Copy-Source.
  1369  		{
  1370  			bucketName:         bucketName,
  1371  			objectName:         objectName,
  1372  			headers:            copySourceHeader,
  1373  			data:               bytesData,
  1374  			dataLen:            len(bytesData),
  1375  			accessKey:          credentials.AccessKey,
  1376  			secretKey:          credentials.SecretKey,
  1377  			expectedRespStatus: http.StatusBadRequest,
  1378  		},
  1379  		// Test case - 4.
  1380  		// Test Case with invalid Content-Md5 value
  1381  		{
  1382  			bucketName:         bucketName,
  1383  			objectName:         objectName,
  1384  			headers:            invalidMD5Header,
  1385  			data:               bytesData,
  1386  			dataLen:            len(bytesData),
  1387  			accessKey:          credentials.AccessKey,
  1388  			secretKey:          credentials.SecretKey,
  1389  			expectedRespStatus: http.StatusBadRequest,
  1390  		},
  1391  		// Test case - 5.
  1392  		// Test Case with object greater than maximum allowed size.
  1393  		{
  1394  			bucketName:         bucketName,
  1395  			objectName:         objectName,
  1396  			data:               bytesData,
  1397  			dataLen:            len(bytesData),
  1398  			accessKey:          credentials.AccessKey,
  1399  			secretKey:          credentials.SecretKey,
  1400  			fault:              TooBigObject,
  1401  			expectedRespStatus: http.StatusBadRequest,
  1402  		},
  1403  		// Test case - 6.
  1404  		// Test Case with missing content length
  1405  		{
  1406  			bucketName:         bucketName,
  1407  			objectName:         objectName,
  1408  			data:               bytesData,
  1409  			dataLen:            len(bytesData),
  1410  			accessKey:          credentials.AccessKey,
  1411  			secretKey:          credentials.SecretKey,
  1412  			fault:              MissingContentLength,
  1413  			expectedRespStatus: http.StatusLengthRequired,
  1414  		},
  1415  		// Test case - 7.
  1416  		// Test Case with invalid header key X-Amz-Storage-Class
  1417  		{
  1418  			bucketName:         bucketName,
  1419  			objectName:         objectName,
  1420  			headers:            inalidStorageClassHeader,
  1421  			data:               bytesData,
  1422  			dataLen:            len(bytesData),
  1423  			accessKey:          credentials.AccessKey,
  1424  			secretKey:          credentials.SecretKey,
  1425  			expectedRespStatus: http.StatusBadRequest,
  1426  		},
  1427  	}
  1428  	// Iterating over the cases, fetching the object validating the response.
  1429  	for i, testCase := range testCases {
  1430  		var req, reqV2 *http.Request
  1431  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
  1432  		rec := httptest.NewRecorder()
  1433  		// construct HTTP request for Get Object end point.
  1434  		req, err = newTestSignedRequestV4(http.MethodPut, getPutObjectURL("", testCase.bucketName, testCase.objectName),
  1435  			int64(testCase.dataLen), bytes.NewReader(testCase.data), testCase.accessKey, testCase.secretKey, nil)
  1436  		if err != nil {
  1437  			t.Fatalf("Test %d: Failed to create HTTP request for Put Object: <ERROR> %v", i+1, err)
  1438  		}
  1439  		// Add test case specific headers to the request.
  1440  		addCustomHeaders(req, testCase.headers)
  1441  
  1442  		// Inject faults if specified in testCase.fault
  1443  		switch testCase.fault {
  1444  		case MissingContentLength:
  1445  			req.ContentLength = -1
  1446  			req.TransferEncoding = []string{}
  1447  		case TooBigObject:
  1448  			req.ContentLength = globalMaxObjectSize + 1
  1449  		}
  1450  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
  1451  		// Call the ServeHTTP to execute the handler,`func (api ObjectAPIHandlers) GetObjectHandler`  handles the request.
  1452  		apiRouter.ServeHTTP(rec, req)
  1453  		// Assert the response code with the expected status.
  1454  		if rec.Code != testCase.expectedRespStatus {
  1455  			t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code)
  1456  		}
  1457  		if testCase.expectedRespStatus == http.StatusOK {
  1458  			buffer := new(bytes.Buffer)
  1459  			// Fetch the object to check whether the content is same as the one uploaded via PutObject.
  1460  			gr, err := obj.GetObjectNInfo(context.Background(), testCase.bucketName, testCase.objectName, nil, nil, readLock, opts)
  1461  			if err != nil {
  1462  				t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
  1463  			}
  1464  			if _, err = io.Copy(buffer, gr); err != nil {
  1465  				gr.Close()
  1466  				t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
  1467  			}
  1468  			gr.Close()
  1469  			if !bytes.Equal(bytesData, buffer.Bytes()) {
  1470  				t.Errorf("Test %d: %s: Data Mismatch: Data fetched back from the uploaded object doesn't match the original one.", i+1, instanceType)
  1471  			}
  1472  			buffer.Reset()
  1473  		}
  1474  
  1475  		// Verify response of the V2 signed HTTP request.
  1476  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
  1477  		recV2 := httptest.NewRecorder()
  1478  		// construct HTTP request for PUT Object endpoint.
  1479  		reqV2, err = newTestSignedRequestV2(http.MethodPut, getPutObjectURL("", testCase.bucketName, testCase.objectName),
  1480  			int64(testCase.dataLen), bytes.NewReader(testCase.data), testCase.accessKey, testCase.secretKey, nil)
  1481  
  1482  		if err != nil {
  1483  			t.Fatalf("Test %d: %s: Failed to create HTTP request for PutObject: <ERROR> %v", i+1, instanceType, err)
  1484  		}
  1485  
  1486  		// Add test case specific headers to the request.
  1487  		addCustomHeaders(reqV2, testCase.headers)
  1488  
  1489  		// Inject faults if specified in testCase.fault
  1490  		switch testCase.fault {
  1491  		case MissingContentLength:
  1492  			reqV2.ContentLength = -1
  1493  			reqV2.TransferEncoding = []string{}
  1494  		case TooBigObject:
  1495  			reqV2.ContentLength = globalMaxObjectSize + 1
  1496  		}
  1497  
  1498  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
  1499  		// Call the ServeHTTP to execute the handler.
  1500  		apiRouter.ServeHTTP(recV2, reqV2)
  1501  		if recV2.Code != testCase.expectedRespStatus {
  1502  			t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code)
  1503  		}
  1504  
  1505  		if testCase.expectedRespStatus == http.StatusOK {
  1506  			buffer := new(bytes.Buffer)
  1507  			// Fetch the object to check whether the content is same as the one uploaded via PutObject.
  1508  			gr, err := obj.GetObjectNInfo(context.Background(), testCase.bucketName, testCase.objectName, nil, nil, readLock, opts)
  1509  			if err != nil {
  1510  				t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
  1511  			}
  1512  			if _, err = io.Copy(buffer, gr); err != nil {
  1513  				gr.Close()
  1514  				t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
  1515  			}
  1516  			gr.Close()
  1517  			if !bytes.Equal(bytesData, buffer.Bytes()) {
  1518  				t.Errorf("Test %d: %s: Data Mismatch: Data fetched back from the uploaded object doesn't match the original one.", i+1, instanceType)
  1519  			}
  1520  			buffer.Reset()
  1521  		}
  1522  	}
  1523  
  1524  	// Test for Anonymous/unsigned http request.
  1525  	anonReq, err := newTestRequest(http.MethodPut, getPutObjectURL("", bucketName, objectName),
  1526  		int64(len("hello")), bytes.NewReader([]byte("hello")))
  1527  	if err != nil {
  1528  		t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
  1529  			instanceType, bucketName, objectName, err)
  1530  	}
  1531  
  1532  	// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
  1533  	// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
  1534  	// unsigned request goes through and its validated again.
  1535  	ExecObjectLayerAPIAnonTest(t, obj, "TestAPIPutObjectHandler", bucketName, objectName, instanceType, apiRouter, anonReq, getAnonWriteOnlyObjectPolicy(bucketName, objectName))
  1536  
  1537  	// HTTP request to test the case of `objectLayer` being set to `nil`.
  1538  	// There is no need to use an existing bucket or valid input for creating the request,
  1539  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
  1540  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
  1541  	nilBucket := "dummy-bucket"
  1542  	nilObject := "dummy-object"
  1543  
  1544  	nilReq, err := newTestSignedRequestV4(http.MethodPut, getPutObjectURL("", nilBucket, nilObject),
  1545  		0, nil, "", "", nil)
  1546  
  1547  	if err != nil {
  1548  		t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
  1549  	}
  1550  	// execute the object layer set to `nil` test.
  1551  	// `ExecObjectLayerAPINilTest` manages the operation.
  1552  	ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
  1553  
  1554  }
  1555  
  1556  // Tests sanity of attempting to copying each parts at offsets from an existing
  1557  // file and create a new object. Also validates if the written is same as what we
  1558  // expected.
  1559  func TestAPICopyObjectPartHandlerSanity(t *testing.T) {
  1560  	defer DetectTestLeak(t)()
  1561  	ExecExtendedObjectLayerAPITest(t, testAPICopyObjectPartHandlerSanity, []string{"CopyObjectPart"})
  1562  }
  1563  
  1564  func testAPICopyObjectPartHandlerSanity(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
  1565  	credentials auth.Credentials, t *testing.T) {
  1566  
  1567  	objectName := "test-object"
  1568  	var err error
  1569  	opts := ObjectOptions{}
  1570  	// set of byte data for PutObject.
  1571  	// object has to be created before running tests for Copy Object.
  1572  	// this is required even to assert the copied object,
  1573  	bytesData := []struct {
  1574  		byteData []byte
  1575  	}{
  1576  		{generateBytesData(6 * humanize.MiByte)},
  1577  	}
  1578  
  1579  	// set of inputs for uploading the objects before tests for downloading is done.
  1580  	putObjectInputs := []struct {
  1581  		bucketName    string
  1582  		objectName    string
  1583  		contentLength int64
  1584  		textData      []byte
  1585  		metaData      map[string]string
  1586  	}{
  1587  		// case - 1.
  1588  		{bucketName, objectName, int64(len(bytesData[0].byteData)), bytesData[0].byteData, make(map[string]string)},
  1589  	}
  1590  	// iterate through the above set of inputs and upload the object.
  1591  	for i, input := range putObjectInputs {
  1592  		// uploading the object.
  1593  		_, err = obj.PutObject(context.Background(), input.bucketName, input.objectName,
  1594  			mustGetPutObjReader(t, bytes.NewReader(input.textData), input.contentLength, input.metaData[""], ""), ObjectOptions{UserDefined: input.metaData})
  1595  		// if object upload fails stop the test.
  1596  		if err != nil {
  1597  			t.Fatalf("Put Object case %d:  Error uploading object: <ERROR> %v", i+1, err)
  1598  		}
  1599  	}
  1600  
  1601  	// Initiate Multipart upload for testing PutObjectPartHandler.
  1602  	testObject := "testobject"
  1603  
  1604  	// PutObjectPart API HTTP Handler has to be tested in isolation,
  1605  	// that is without any other handler being registered,
  1606  	// That's why NewMultipartUpload is initiated using ObjectLayer.
  1607  	uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
  1608  	if err != nil {
  1609  		// Failed to create NewMultipartUpload, abort.
  1610  		t.Fatalf("MinIO %s : <ERROR>  %s", instanceType, err)
  1611  	}
  1612  
  1613  	a := 0
  1614  	b := globalMinPartSize
  1615  	var parts []CompletePart
  1616  	for partNumber := 1; partNumber <= 2; partNumber++ {
  1617  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
  1618  		rec := httptest.NewRecorder()
  1619  		cpPartURL := getCopyObjectPartURL("", bucketName, testObject, uploadID, fmt.Sprintf("%d", partNumber))
  1620  
  1621  		// construct HTTP request for copy object.
  1622  		var req *http.Request
  1623  		req, err = newTestSignedRequestV4(http.MethodPut, cpPartURL, 0, nil, credentials.AccessKey, credentials.SecretKey, nil)
  1624  		if err != nil {
  1625  			t.Fatalf("Test failed to create HTTP request for copy object part: <ERROR> %v", err)
  1626  		}
  1627  
  1628  		// "X-Amz-Copy-Source" header contains the information about the source bucket and the object to copied.
  1629  		req.Header.Set("X-Amz-Copy-Source", url.QueryEscape(pathJoin(bucketName, objectName)))
  1630  		req.Header.Set("X-Amz-Copy-Source-Range", fmt.Sprintf("bytes=%d-%d", a, b))
  1631  
  1632  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
  1633  		// Call the ServeHTTP to execute the handler, `func (api ObjectAPIHandlers) CopyObjectHandler` handles the request.
  1634  		a = globalMinPartSize + 1
  1635  		b = len(bytesData[0].byteData) - 1
  1636  		apiRouter.ServeHTTP(rec, req)
  1637  		if rec.Code != http.StatusOK {
  1638  			t.Fatalf("Test failed to create HTTP request for copy %d", rec.Code)
  1639  		}
  1640  
  1641  		resp := &CopyObjectPartResponse{}
  1642  		if err = xmlDecoder(rec.Body, resp, rec.Result().ContentLength); err != nil {
  1643  			t.Fatalf("Test failed to decode XML response: <ERROR> %v", err)
  1644  		}
  1645  
  1646  		parts = append(parts, CompletePart{
  1647  			PartNumber: partNumber,
  1648  			ETag:       canonicalizeETag(resp.ETag),
  1649  		})
  1650  	}
  1651  
  1652  	result, err := obj.CompleteMultipartUpload(context.Background(), bucketName, testObject, uploadID, parts, ObjectOptions{})
  1653  	if err != nil {
  1654  		t.Fatalf("Test: %s complete multipart upload failed: <ERROR> %v", instanceType, err)
  1655  	}
  1656  	if result.Size != int64(len(bytesData[0].byteData)) {
  1657  		t.Fatalf("Test: %s expected size not written: expected %d, got %d", instanceType, len(bytesData[0].byteData), result.Size)
  1658  	}
  1659  
  1660  	var buf bytes.Buffer
  1661  	r, err := obj.GetObjectNInfo(context.Background(), bucketName, testObject, nil, nil, readLock, ObjectOptions{})
  1662  	if err != nil {
  1663  		t.Fatalf("Test: %s reading completed file failed: <ERROR> %v", instanceType, err)
  1664  	}
  1665  	if _, err = io.Copy(&buf, r); err != nil {
  1666  		r.Close()
  1667  		t.Fatalf("Test %s: Failed to fetch the copied object: <ERROR> %s", instanceType, err)
  1668  	}
  1669  	r.Close()
  1670  	if !bytes.Equal(buf.Bytes(), bytesData[0].byteData) {
  1671  		t.Fatalf("Test: %s returned data is not expected corruption detected:", instanceType)
  1672  	}
  1673  }
  1674  
  1675  // Wrapper for calling Copy Object Part API handler tests for both Erasure multiple disks and single node setup.
  1676  func TestAPICopyObjectPartHandler(t *testing.T) {
  1677  	defer DetectTestLeak(t)()
  1678  	ExecExtendedObjectLayerAPITest(t, testAPICopyObjectPartHandler, []string{"CopyObjectPart"})
  1679  }
  1680  
  1681  func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
  1682  	credentials auth.Credentials, t *testing.T) {
  1683  
  1684  	objectName := "test-object"
  1685  	var err error
  1686  	opts := ObjectOptions{}
  1687  	// set of byte data for PutObject.
  1688  	// object has to be created before running tests for Copy Object.
  1689  	// this is required even to assert the copied object,
  1690  	bytesData := []struct {
  1691  		byteData []byte
  1692  	}{
  1693  		{generateBytesData(6 * humanize.KiByte)},
  1694  	}
  1695  
  1696  	// set of inputs for uploading the objects before tests for downloading is done.
  1697  	putObjectInputs := []struct {
  1698  		bucketName    string
  1699  		objectName    string
  1700  		contentLength int64
  1701  		textData      []byte
  1702  		metaData      map[string]string
  1703  	}{
  1704  		// case - 1.
  1705  		{bucketName, objectName, int64(len(bytesData[0].byteData)), bytesData[0].byteData, make(map[string]string)},
  1706  	}
  1707  	// iterate through the above set of inputs and upload the object.
  1708  	for i, input := range putObjectInputs {
  1709  		// uploading the object.
  1710  		_, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewReader(input.textData), input.contentLength, input.metaData[""], ""), ObjectOptions{UserDefined: input.metaData})
  1711  		// if object upload fails stop the test.
  1712  		if err != nil {
  1713  			t.Fatalf("Put Object case %d:  Error uploading object: <ERROR> %v", i+1, err)
  1714  		}
  1715  	}
  1716  
  1717  	// Initiate Multipart upload for testing PutObjectPartHandler.
  1718  	testObject := "testobject"
  1719  
  1720  	// PutObjectPart API HTTP Handler has to be tested in isolation,
  1721  	// that is without any other handler being registered,
  1722  	// That's why NewMultipartUpload is initiated using ObjectLayer.
  1723  	uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
  1724  	if err != nil {
  1725  		// Failed to create NewMultipartUpload, abort.
  1726  		t.Fatalf("MinIO %s : <ERROR>  %s", instanceType, err)
  1727  	}
  1728  
  1729  	// test cases with inputs and expected result for Copy Object.
  1730  	testCases := []struct {
  1731  		bucketName        string
  1732  		copySourceHeader  string // data for "X-Amz-Copy-Source" header. Contains the object to be copied in the URL.
  1733  		copySourceRange   string // data for "X-Amz-Copy-Source-Range" header, contains the byte range offsets of data to be copied.
  1734  		uploadID          string // uploadID of the transaction.
  1735  		invalidPartNumber bool   // Sets an invalid multipart.
  1736  		maximumPartNumber bool   // Sets a maximum parts.
  1737  		accessKey         string
  1738  		secretKey         string
  1739  		// expected output.
  1740  		expectedRespStatus int
  1741  	}{
  1742  		// Test case - 1, copy part 1 from from newObject1, ignore request headers.
  1743  		{
  1744  			bucketName:         bucketName,
  1745  			uploadID:           uploadID,
  1746  			copySourceHeader:   url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  1747  			accessKey:          credentials.AccessKey,
  1748  			secretKey:          credentials.SecretKey,
  1749  			expectedRespStatus: http.StatusOK,
  1750  		},
  1751  
  1752  		// Test case - 2.
  1753  		// Test case with invalid source object.
  1754  		{
  1755  			bucketName:       bucketName,
  1756  			uploadID:         uploadID,
  1757  			copySourceHeader: url.QueryEscape(SlashSeparator),
  1758  			accessKey:        credentials.AccessKey,
  1759  			secretKey:        credentials.SecretKey,
  1760  
  1761  			expectedRespStatus: http.StatusBadRequest,
  1762  		},
  1763  
  1764  		// Test case - 3.
  1765  		// Test case with new object name is same as object to be copied.
  1766  		// Fail with file not found.
  1767  		{
  1768  			bucketName:       bucketName,
  1769  			uploadID:         uploadID,
  1770  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + testObject),
  1771  			accessKey:        credentials.AccessKey,
  1772  			secretKey:        credentials.SecretKey,
  1773  
  1774  			expectedRespStatus: http.StatusNotFound,
  1775  		},
  1776  
  1777  		// Test case - 4.
  1778  		// Test case with valid byte range.
  1779  		{
  1780  			bucketName:       bucketName,
  1781  			uploadID:         uploadID,
  1782  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  1783  			copySourceRange:  "bytes=500-4096",
  1784  			accessKey:        credentials.AccessKey,
  1785  			secretKey:        credentials.SecretKey,
  1786  
  1787  			expectedRespStatus: http.StatusOK,
  1788  		},
  1789  
  1790  		// Test case - 5.
  1791  		// Test case with invalid byte range.
  1792  		{
  1793  			bucketName:       bucketName,
  1794  			uploadID:         uploadID,
  1795  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  1796  			copySourceRange:  "bytes=6145-",
  1797  			accessKey:        credentials.AccessKey,
  1798  			secretKey:        credentials.SecretKey,
  1799  
  1800  			expectedRespStatus: http.StatusBadRequest,
  1801  		},
  1802  
  1803  		// Test case - 6.
  1804  		// Test case with ivalid byte range for exceeding source size boundaries.
  1805  		{
  1806  			bucketName:       bucketName,
  1807  			uploadID:         uploadID,
  1808  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  1809  			copySourceRange:  "bytes=0-6144",
  1810  			accessKey:        credentials.AccessKey,
  1811  			secretKey:        credentials.SecretKey,
  1812  
  1813  			expectedRespStatus: http.StatusBadRequest,
  1814  		},
  1815  
  1816  		// Test case - 7.
  1817  		// Test case with object name missing from source.
  1818  		// fail with BadRequest.
  1819  		{
  1820  			bucketName:       bucketName,
  1821  			uploadID:         uploadID,
  1822  			copySourceHeader: url.QueryEscape("//123"),
  1823  			accessKey:        credentials.AccessKey,
  1824  			secretKey:        credentials.SecretKey,
  1825  
  1826  			expectedRespStatus: http.StatusBadRequest,
  1827  		},
  1828  
  1829  		// Test case - 8.
  1830  		// Test case with non-existent source file.
  1831  		// Case for the purpose of failing `api.ObjectAPI.GetObjectInfo`.
  1832  		// Expecting the response status code to http.StatusNotFound (404).
  1833  		{
  1834  			bucketName:       bucketName,
  1835  			uploadID:         uploadID,
  1836  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + "non-existent-object"),
  1837  			accessKey:        credentials.AccessKey,
  1838  			secretKey:        credentials.SecretKey,
  1839  
  1840  			expectedRespStatus: http.StatusNotFound,
  1841  		},
  1842  
  1843  		// Test case - 9.
  1844  		// Test case with non-existent source file.
  1845  		// Case for the purpose of failing `api.ObjectAPI.PutObjectPart`.
  1846  		// Expecting the response status code to http.StatusNotFound (404).
  1847  		{
  1848  			bucketName:       "non-existent-destination-bucket",
  1849  			uploadID:         uploadID,
  1850  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  1851  			accessKey:        credentials.AccessKey,
  1852  			secretKey:        credentials.SecretKey,
  1853  
  1854  			expectedRespStatus: http.StatusNotFound,
  1855  		},
  1856  
  1857  		// Test case - 10.
  1858  		// Case with invalid AccessKey.
  1859  		{
  1860  			bucketName:       bucketName,
  1861  			uploadID:         uploadID,
  1862  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  1863  			accessKey:        "Invalid-AccessID",
  1864  			secretKey:        credentials.SecretKey,
  1865  
  1866  			expectedRespStatus: http.StatusForbidden,
  1867  		},
  1868  
  1869  		// Test case - 11.
  1870  		// Case with non-existent upload id.
  1871  		{
  1872  			bucketName:       bucketName,
  1873  			uploadID:         "-1",
  1874  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  1875  			accessKey:        credentials.AccessKey,
  1876  			secretKey:        credentials.SecretKey,
  1877  
  1878  			expectedRespStatus: http.StatusNotFound,
  1879  		},
  1880  		// Test case - 12.
  1881  		// invalid part number.
  1882  		{
  1883  			bucketName:         bucketName,
  1884  			uploadID:           uploadID,
  1885  			copySourceHeader:   url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  1886  			invalidPartNumber:  true,
  1887  			accessKey:          credentials.AccessKey,
  1888  			secretKey:          credentials.SecretKey,
  1889  			expectedRespStatus: http.StatusOK,
  1890  		},
  1891  		// Test case - 13.
  1892  		// maximum part number.
  1893  		{
  1894  			bucketName:         bucketName,
  1895  			uploadID:           uploadID,
  1896  			copySourceHeader:   url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  1897  			maximumPartNumber:  true,
  1898  			accessKey:          credentials.AccessKey,
  1899  			secretKey:          credentials.SecretKey,
  1900  			expectedRespStatus: http.StatusOK,
  1901  		},
  1902  		// Test case - 14, copy part 1 from from newObject1 with null versionId
  1903  		{
  1904  			bucketName:         bucketName,
  1905  			uploadID:           uploadID,
  1906  			copySourceHeader:   url.QueryEscape(SlashSeparator+bucketName+SlashSeparator+objectName) + "?versionId=null",
  1907  			accessKey:          credentials.AccessKey,
  1908  			secretKey:          credentials.SecretKey,
  1909  			expectedRespStatus: http.StatusOK,
  1910  		},
  1911  		// Test case - 15, copy part 1 from from newObject1 with non null versionId
  1912  		{
  1913  			bucketName:         bucketName,
  1914  			uploadID:           uploadID,
  1915  			copySourceHeader:   url.QueryEscape(SlashSeparator+bucketName+SlashSeparator+objectName) + "?versionId=17",
  1916  			accessKey:          credentials.AccessKey,
  1917  			secretKey:          credentials.SecretKey,
  1918  			expectedRespStatus: http.StatusNotFound,
  1919  		},
  1920  	}
  1921  
  1922  	for i, testCase := range testCases {
  1923  		var req *http.Request
  1924  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
  1925  		rec := httptest.NewRecorder()
  1926  		if !testCase.invalidPartNumber || !testCase.maximumPartNumber {
  1927  			// construct HTTP request for copy object.
  1928  			req, err = newTestSignedRequestV4(http.MethodPut, getCopyObjectPartURL("", testCase.bucketName, testObject, testCase.uploadID, "1"), 0, nil, testCase.accessKey, testCase.secretKey, nil)
  1929  		} else if testCase.invalidPartNumber {
  1930  			req, err = newTestSignedRequestV4(http.MethodPut, getCopyObjectPartURL("", testCase.bucketName, testObject, testCase.uploadID, "abc"), 0, nil, testCase.accessKey, testCase.secretKey, nil)
  1931  		} else if testCase.maximumPartNumber {
  1932  			req, err = newTestSignedRequestV4(http.MethodPut, getCopyObjectPartURL("", testCase.bucketName, testObject, testCase.uploadID, "99999"), 0, nil, testCase.accessKey, testCase.secretKey, nil)
  1933  		}
  1934  		if err != nil {
  1935  			t.Fatalf("Test %d: Failed to create HTTP request for copy Object: <ERROR> %v", i+1, err)
  1936  		}
  1937  
  1938  		// "X-Amz-Copy-Source" header contains the information about the source bucket and the object to copied.
  1939  		if testCase.copySourceHeader != "" {
  1940  			req.Header.Set("X-Amz-Copy-Source", testCase.copySourceHeader)
  1941  		}
  1942  		if testCase.copySourceRange != "" {
  1943  			req.Header.Set("X-Amz-Copy-Source-Range", testCase.copySourceRange)
  1944  		}
  1945  
  1946  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
  1947  		// Call the ServeHTTP to execute the handler, `func (api ObjectAPIHandlers) CopyObjectHandler` handles the request.
  1948  		apiRouter.ServeHTTP(rec, req)
  1949  		// Assert the response code with the expected status.
  1950  		if rec.Code != testCase.expectedRespStatus {
  1951  			t.Fatalf("Test %d: %s:  Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
  1952  		}
  1953  		if rec.Code == http.StatusOK {
  1954  			// See if the new part has been uploaded.
  1955  			// testing whether the copy was successful.
  1956  			var results ListPartsInfo
  1957  			results, err = obj.ListObjectParts(context.Background(), testCase.bucketName, testObject, testCase.uploadID, 0, 1, ObjectOptions{})
  1958  			if err != nil {
  1959  				t.Fatalf("Test %d: %s: Failed to look for copied object part: <ERROR> %s", i+1, instanceType, err)
  1960  			}
  1961  			if instanceType != FSTestStr && len(results.Parts) != 1 {
  1962  				t.Fatalf("Test %d: %s: Expected only one entry returned %d entries", i+1, instanceType, len(results.Parts))
  1963  			}
  1964  		}
  1965  	}
  1966  
  1967  	// HTTP request for testing when `ObjectLayer` is set to `nil`.
  1968  	// There is no need to use an existing bucket and valid input for creating the request
  1969  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
  1970  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
  1971  	nilBucket := "dummy-bucket"
  1972  	nilObject := "dummy-object"
  1973  
  1974  	nilReq, err := newTestSignedRequestV4(http.MethodPut, getCopyObjectPartURL("", nilBucket, nilObject, "0", "0"),
  1975  		0, bytes.NewReader([]byte("testNilObjLayer")), "", "", nil)
  1976  	if err != nil {
  1977  		t.Errorf("MinIO %s: Failed to create http request for testing the response when object Layer is set to `nil`.", instanceType)
  1978  	}
  1979  
  1980  	// Below is how CopyObjectPartHandler is registered.
  1981  	// bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(api.CopyObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
  1982  	// Its necessary to set the "X-Amz-Copy-Source" header for the request to be accepted by the handler.
  1983  	nilReq.Header.Set("X-Amz-Copy-Source", url.QueryEscape(SlashSeparator+nilBucket+SlashSeparator+nilObject))
  1984  
  1985  	// execute the object layer set to `nil` test.
  1986  	// `ExecObjectLayerAPINilTest` manages the operation.
  1987  	ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
  1988  
  1989  }
  1990  
  1991  // Wrapper for calling Copy Object API handler tests for both Erasure multiple disks and single node setup.
  1992  func TestAPICopyObjectHandler(t *testing.T) {
  1993  	defer DetectTestLeak(t)()
  1994  	ExecExtendedObjectLayerAPITest(t, testAPICopyObjectHandler, []string{"CopyObject"})
  1995  }
  1996  
  1997  func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
  1998  	credentials auth.Credentials, t *testing.T) {
  1999  
  2000  	objectName := "test?object" // use file with ? to test URL parsing...
  2001  	if runtime.GOOS == "windows" {
  2002  		objectName = "test-object" // ...except on Windows
  2003  	}
  2004  	// object used for anonymous HTTP request test.
  2005  	anonObject := "anon-object"
  2006  	var err error
  2007  	opts := ObjectOptions{}
  2008  	// set of byte data for PutObject.
  2009  	// object has to be created before running tests for Copy Object.
  2010  	// this is required even to assert the copied object,
  2011  	bytesData := []struct {
  2012  		byteData []byte
  2013  		md5sum   string
  2014  	}{
  2015  		{byteData: generateBytesData(6 * humanize.KiByte)},
  2016  	}
  2017  	h := md5.New()
  2018  	h.Write(bytesData[0].byteData)
  2019  	bytesData[0].md5sum = hex.EncodeToString(h.Sum(nil))
  2020  
  2021  	buffers := []*bytes.Buffer{
  2022  		new(bytes.Buffer),
  2023  		new(bytes.Buffer),
  2024  	}
  2025  
  2026  	// set of inputs for uploading the objects before tests for downloading is done.
  2027  	putObjectInputs := []struct {
  2028  		bucketName    string
  2029  		objectName    string
  2030  		contentLength int64
  2031  		textData      []byte
  2032  		md5sum        string
  2033  		metaData      map[string]string
  2034  	}{
  2035  		// case - 1.
  2036  		{bucketName, objectName, int64(len(bytesData[0].byteData)), bytesData[0].byteData, bytesData[0].md5sum, make(map[string]string)},
  2037  
  2038  		// case - 2.
  2039  		// used for anonymous HTTP request test.
  2040  		{bucketName, anonObject, int64(len(bytesData[0].byteData)), bytesData[0].byteData, bytesData[0].md5sum, make(map[string]string)},
  2041  	}
  2042  
  2043  	// iterate through the above set of inputs and upload the object.
  2044  	for i, input := range putObjectInputs {
  2045  		// uploading the object.
  2046  		var objInfo ObjectInfo
  2047  		objInfo, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewReader(input.textData), input.contentLength, input.md5sum, ""), ObjectOptions{UserDefined: input.metaData})
  2048  		// if object upload fails stop the test.
  2049  		if err != nil {
  2050  			t.Fatalf("Put Object case %d:  Error uploading object: <ERROR> %v", i+1, err)
  2051  		}
  2052  		if objInfo.ETag != input.md5sum {
  2053  			t.Fatalf("Put Object case %d:  Checksum mismatched: <ERROR> got %s, expected %s", i+1, input.md5sum, objInfo.ETag)
  2054  		}
  2055  	}
  2056  
  2057  	// test cases with inputs and expected result for Copy Object.
  2058  	testCases := []struct {
  2059  		bucketName           string
  2060  		newObjectName        string // name of the newly copied object.
  2061  		copySourceHeader     string // data for "X-Amz-Copy-Source" header. Contains the object to be copied in the URL.
  2062  		copyModifiedHeader   string // data for "X-Amz-Copy-Source-If-Modified-Since" header
  2063  		copyUnmodifiedHeader string // data for "X-Amz-Copy-Source-If-Unmodified-Since" header
  2064  		copySourceSame       bool
  2065  		metadataGarbage      bool
  2066  		metadataReplace      bool
  2067  		metadataCopy         bool
  2068  		metadata             map[string]string
  2069  		accessKey            string
  2070  		secretKey            string
  2071  		// expected output.
  2072  		expectedRespStatus int
  2073  	}{
  2074  		0: {
  2075  			expectedRespStatus: http.StatusMethodNotAllowed,
  2076  		},
  2077  		// Test case - 1, copy metadata from newObject1, ignore request headers.
  2078  		1: {
  2079  			bucketName:       bucketName,
  2080  			newObjectName:    "newObject1",
  2081  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  2082  			accessKey:        credentials.AccessKey,
  2083  			secretKey:        credentials.SecretKey,
  2084  			metadata: map[string]string{
  2085  				"Content-Type": "application/json",
  2086  			},
  2087  			expectedRespStatus: http.StatusOK,
  2088  		},
  2089  
  2090  		// Test case - 2.
  2091  		// Test case with invalid source object.
  2092  		2: {
  2093  			bucketName:       bucketName,
  2094  			newObjectName:    "newObject1",
  2095  			copySourceHeader: url.QueryEscape(SlashSeparator),
  2096  			accessKey:        credentials.AccessKey,
  2097  			secretKey:        credentials.SecretKey,
  2098  
  2099  			expectedRespStatus: http.StatusBadRequest,
  2100  		},
  2101  
  2102  		// Test case - 3.
  2103  		// Test case with new object name is same as object to be copied.
  2104  		3: {
  2105  			bucketName:       bucketName,
  2106  			newObjectName:    objectName,
  2107  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  2108  			accessKey:        credentials.AccessKey,
  2109  			copySourceSame:   true,
  2110  			secretKey:        credentials.SecretKey,
  2111  
  2112  			expectedRespStatus: http.StatusBadRequest,
  2113  		},
  2114  
  2115  		// Test case - 4.
  2116  		// Test case with new object name is same as object to be copied.
  2117  		// But source copy is without leading slash
  2118  		4: {
  2119  			bucketName:       bucketName,
  2120  			newObjectName:    objectName,
  2121  			copySourceSame:   true,
  2122  			copySourceHeader: url.QueryEscape(bucketName + SlashSeparator + objectName),
  2123  			accessKey:        credentials.AccessKey,
  2124  			secretKey:        credentials.SecretKey,
  2125  
  2126  			expectedRespStatus: http.StatusBadRequest,
  2127  		},
  2128  
  2129  		// Test case - 5.
  2130  		// Test case with new object name is same as object to be copied
  2131  		// but metadata is updated.
  2132  		5: {
  2133  			bucketName:       bucketName,
  2134  			newObjectName:    objectName,
  2135  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  2136  			metadata: map[string]string{
  2137  				"Content-Type": "application/json",
  2138  			},
  2139  			metadataReplace: true,
  2140  			accessKey:       credentials.AccessKey,
  2141  			secretKey:       credentials.SecretKey,
  2142  
  2143  			expectedRespStatus: http.StatusOK,
  2144  		},
  2145  
  2146  		// Test case - 6.
  2147  		// Test case with invalid metadata-directive.
  2148  		6: {
  2149  			bucketName:       bucketName,
  2150  			newObjectName:    "newObject1",
  2151  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  2152  			metadata: map[string]string{
  2153  				"Content-Type": "application/json",
  2154  			},
  2155  			metadataGarbage: true,
  2156  			accessKey:       credentials.AccessKey,
  2157  			secretKey:       credentials.SecretKey,
  2158  
  2159  			expectedRespStatus: http.StatusBadRequest,
  2160  		},
  2161  
  2162  		// Test case - 7.
  2163  		// Test case with new object name is same as object to be copied
  2164  		// fail with BadRequest.
  2165  		7: {
  2166  			bucketName:       bucketName,
  2167  			newObjectName:    objectName,
  2168  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  2169  			metadata: map[string]string{
  2170  				"Content-Type": "application/json",
  2171  			},
  2172  			copySourceSame: true,
  2173  			metadataCopy:   true,
  2174  			accessKey:      credentials.AccessKey,
  2175  			secretKey:      credentials.SecretKey,
  2176  
  2177  			expectedRespStatus: http.StatusBadRequest,
  2178  		},
  2179  
  2180  		// Test case - 8.
  2181  		// Test case with non-existent source file.
  2182  		// Case for the purpose of failing `api.ObjectAPI.GetObjectInfo`.
  2183  		// Expecting the response status code to http.StatusNotFound (404).
  2184  		8: {
  2185  			bucketName:       bucketName,
  2186  			newObjectName:    objectName,
  2187  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + "non-existent-object"),
  2188  			accessKey:        credentials.AccessKey,
  2189  			secretKey:        credentials.SecretKey,
  2190  
  2191  			expectedRespStatus: http.StatusNotFound,
  2192  		},
  2193  
  2194  		// Test case - 9.
  2195  		// Test case with non-existent source file.
  2196  		// Case for the purpose of failing `api.ObjectAPI.PutObject`.
  2197  		// Expecting the response status code to http.StatusNotFound (404).
  2198  		9: {
  2199  			bucketName:       "non-existent-destination-bucket",
  2200  			newObjectName:    objectName,
  2201  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  2202  			accessKey:        credentials.AccessKey,
  2203  			secretKey:        credentials.SecretKey,
  2204  
  2205  			expectedRespStatus: http.StatusNotFound,
  2206  		},
  2207  
  2208  		// Test case - 10.
  2209  		// Case with invalid AccessKey.
  2210  		10: {
  2211  			bucketName:       bucketName,
  2212  			newObjectName:    objectName,
  2213  			copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  2214  			accessKey:        "Invalid-AccessID",
  2215  			secretKey:        credentials.SecretKey,
  2216  
  2217  			expectedRespStatus: http.StatusForbidden,
  2218  		},
  2219  		// Test case - 11, copy metadata from newObject1 with satisfying modified header.
  2220  		11: {
  2221  			bucketName:         bucketName,
  2222  			newObjectName:      "newObject1",
  2223  			copySourceHeader:   url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  2224  			copyModifiedHeader: "Mon, 02 Jan 2006 15:04:05 GMT",
  2225  			accessKey:          credentials.AccessKey,
  2226  			secretKey:          credentials.SecretKey,
  2227  			expectedRespStatus: http.StatusOK,
  2228  		},
  2229  		// Test case - 12, copy metadata from newObject1 with unsatisfying modified header.
  2230  		12: {
  2231  			bucketName:         bucketName,
  2232  			newObjectName:      "newObject1",
  2233  			copySourceHeader:   url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  2234  			copyModifiedHeader: "Mon, 02 Jan 2217 15:04:05 GMT",
  2235  			accessKey:          credentials.AccessKey,
  2236  			secretKey:          credentials.SecretKey,
  2237  			expectedRespStatus: http.StatusPreconditionFailed,
  2238  		},
  2239  		// Test case - 13, copy metadata from newObject1 with wrong modified header format
  2240  		13: {
  2241  			bucketName:         bucketName,
  2242  			newObjectName:      "newObject1",
  2243  			copySourceHeader:   url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  2244  			copyModifiedHeader: "Mon, 02 Jan 2217 15:04:05 +00:00",
  2245  			accessKey:          credentials.AccessKey,
  2246  			secretKey:          credentials.SecretKey,
  2247  			expectedRespStatus: http.StatusOK,
  2248  		},
  2249  		// Test case - 14, copy metadata from newObject1 with satisfying unmodified header.
  2250  		14: {
  2251  			bucketName:           bucketName,
  2252  			newObjectName:        "newObject1",
  2253  			copySourceHeader:     url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  2254  			copyUnmodifiedHeader: "Mon, 02 Jan 2217 15:04:05 GMT",
  2255  			accessKey:            credentials.AccessKey,
  2256  			secretKey:            credentials.SecretKey,
  2257  			expectedRespStatus:   http.StatusOK,
  2258  		},
  2259  		// Test case - 15, copy metadata from newObject1 with unsatisfying unmodified header.
  2260  		15: {
  2261  			bucketName:           bucketName,
  2262  			newObjectName:        "newObject1",
  2263  			copySourceHeader:     url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  2264  			copyUnmodifiedHeader: "Mon, 02 Jan 2007 15:04:05 GMT",
  2265  			accessKey:            credentials.AccessKey,
  2266  			secretKey:            credentials.SecretKey,
  2267  			expectedRespStatus:   http.StatusPreconditionFailed,
  2268  		},
  2269  		// Test case - 16, copy metadata from newObject1 with incorrect unmodified header format.
  2270  		16: {
  2271  			bucketName:           bucketName,
  2272  			newObjectName:        "newObject1",
  2273  			copySourceHeader:     url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
  2274  			copyUnmodifiedHeader: "Mon, 02 Jan 2007 15:04:05 +00:00",
  2275  			accessKey:            credentials.AccessKey,
  2276  			secretKey:            credentials.SecretKey,
  2277  			expectedRespStatus:   http.StatusOK,
  2278  		},
  2279  		// Test case - 17, copy metadata from newObject1 with null versionId
  2280  		17: {
  2281  			bucketName:         bucketName,
  2282  			newObjectName:      "newObject1",
  2283  			copySourceHeader:   url.QueryEscape(SlashSeparator+bucketName+SlashSeparator+objectName) + "?versionId=null",
  2284  			accessKey:          credentials.AccessKey,
  2285  			secretKey:          credentials.SecretKey,
  2286  			expectedRespStatus: http.StatusOK,
  2287  		},
  2288  		// Test case - 18, copy metadata from newObject1 with non null versionId
  2289  		18: {
  2290  			bucketName:         bucketName,
  2291  			newObjectName:      "newObject1",
  2292  			copySourceHeader:   url.QueryEscape(SlashSeparator+bucketName+SlashSeparator+objectName) + "?versionId=17",
  2293  			accessKey:          credentials.AccessKey,
  2294  			secretKey:          credentials.SecretKey,
  2295  			expectedRespStatus: http.StatusNotFound,
  2296  		},
  2297  	}
  2298  
  2299  	for i, testCase := range testCases {
  2300  		var req *http.Request
  2301  		var reqV2 *http.Request
  2302  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
  2303  		rec := httptest.NewRecorder()
  2304  		// construct HTTP request for copy object.
  2305  		req, err = newTestSignedRequestV4(http.MethodPut, getCopyObjectURL("", testCase.bucketName, testCase.newObjectName),
  2306  			0, nil, testCase.accessKey, testCase.secretKey, nil)
  2307  
  2308  		if err != nil {
  2309  			t.Fatalf("Test %d: Failed to create HTTP request for copy Object: <ERROR> %v", i, err)
  2310  		}
  2311  		// "X-Amz-Copy-Source" header contains the information about the source bucket and the object to copied.
  2312  		if testCase.copySourceHeader != "" {
  2313  			req.Header.Set("X-Amz-Copy-Source", testCase.copySourceHeader)
  2314  		}
  2315  		if testCase.copyModifiedHeader != "" {
  2316  			req.Header.Set("X-Amz-Copy-Source-If-Modified-Since", testCase.copyModifiedHeader)
  2317  		}
  2318  		if testCase.copyUnmodifiedHeader != "" {
  2319  			req.Header.Set("X-Amz-Copy-Source-If-Unmodified-Since", testCase.copyUnmodifiedHeader)
  2320  		}
  2321  		// Add custom metadata.
  2322  		for k, v := range testCase.metadata {
  2323  			req.Header.Set(k, v)
  2324  		}
  2325  		if testCase.metadataReplace {
  2326  			req.Header.Set("X-Amz-Metadata-Directive", "REPLACE")
  2327  		}
  2328  		if testCase.metadataCopy {
  2329  			req.Header.Set("X-Amz-Metadata-Directive", "COPY")
  2330  		}
  2331  		if testCase.metadataGarbage {
  2332  			req.Header.Set("X-Amz-Metadata-Directive", "Unknown")
  2333  		}
  2334  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
  2335  		// Call the ServeHTTP to execute the handler, `func (api ObjectAPIHandlers) CopyObjectHandler` handles the request.
  2336  		apiRouter.ServeHTTP(rec, req)
  2337  		// Assert the response code with the expected status.
  2338  		if rec.Code != testCase.expectedRespStatus {
  2339  			if testCase.copySourceSame {
  2340  				// encryption will rotate creds, so fail only for non-encryption scenario.
  2341  				if GlobalKMS == nil {
  2342  					t.Errorf("Test %d: %s:  Expected the response status to be `%d`, but instead found `%d`", i, instanceType, testCase.expectedRespStatus, rec.Code)
  2343  					continue
  2344  				}
  2345  			} else {
  2346  				t.Errorf("Test %d: %s:  Expected the response status to be `%d`, but instead found `%d`", i, instanceType, testCase.expectedRespStatus, rec.Code)
  2347  				continue
  2348  			}
  2349  		}
  2350  		if rec.Code == http.StatusOK {
  2351  			var cpObjResp CopyObjectResponse
  2352  			if err = xml.Unmarshal(rec.Body.Bytes(), &cpObjResp); err != nil {
  2353  				t.Fatalf("Test %d: %s: Failed to parse the CopyObjectResult response: <ERROR> %s", i, instanceType, err)
  2354  			}
  2355  
  2356  			// See if the new object is formed.
  2357  			// testing whether the copy was successful.
  2358  			// Note that this goes directly to the file system,
  2359  			// so encryption/compression may interfere at some point.
  2360  			buffers[0].Reset()
  2361  			r, err := obj.GetObjectNInfo(context.Background(), testCase.bucketName, testCase.newObjectName, nil, nil, readLock, opts)
  2362  			if err != nil {
  2363  				t.Fatalf("Test %d: %s reading completed file failed: <ERROR> %v", i, instanceType, err)
  2364  			}
  2365  			if _, err = io.Copy(buffers[0], r); err != nil {
  2366  				r.Close()
  2367  				t.Fatalf("Test %d %s: Failed to fetch the copied object: <ERROR> %s", i, instanceType, err)
  2368  			}
  2369  			r.Close()
  2370  			if !bytes.Equal(bytesData[0].byteData, buffers[0].Bytes()) {
  2371  				t.Errorf("Test %d: %s: Data Mismatch: Data fetched back from the copied object doesn't match the original one.", i, instanceType)
  2372  			}
  2373  		}
  2374  
  2375  		// Verify response of the V2 signed HTTP request.
  2376  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
  2377  		recV2 := httptest.NewRecorder()
  2378  
  2379  		reqV2, err = newTestRequest(http.MethodPut, getCopyObjectURL("", testCase.bucketName, testCase.newObjectName), 0, nil)
  2380  		if err != nil {
  2381  			t.Fatalf("Test %d: Failed to create HTTP request for copy Object: <ERROR> %v", i, err)
  2382  		}
  2383  		// "X-Amz-Copy-Source" header contains the information about the source bucket and the object to copied.
  2384  		if testCase.copySourceHeader != "" {
  2385  			reqV2.Header.Set("X-Amz-Copy-Source", testCase.copySourceHeader)
  2386  		}
  2387  		if testCase.copyModifiedHeader != "" {
  2388  			reqV2.Header.Set("X-Amz-Copy-Source-If-Modified-Since", testCase.copyModifiedHeader)
  2389  		}
  2390  		if testCase.copyUnmodifiedHeader != "" {
  2391  			reqV2.Header.Set("X-Amz-Copy-Source-If-Unmodified-Since", testCase.copyUnmodifiedHeader)
  2392  		}
  2393  
  2394  		// Add custom metadata.
  2395  		for k, v := range testCase.metadata {
  2396  			reqV2.Header.Set(k, v+"+x")
  2397  		}
  2398  		if testCase.metadataReplace {
  2399  			reqV2.Header.Set("X-Amz-Metadata-Directive", "REPLACE")
  2400  		}
  2401  		if testCase.metadataCopy {
  2402  			reqV2.Header.Set("X-Amz-Metadata-Directive", "COPY")
  2403  		}
  2404  		if testCase.metadataGarbage {
  2405  			reqV2.Header.Set("X-Amz-Metadata-Directive", "Unknown")
  2406  		}
  2407  
  2408  		err = signRequestV2(reqV2, testCase.accessKey, testCase.secretKey)
  2409  
  2410  		if err != nil {
  2411  			t.Fatalf("Failed to V2 Sign the HTTP request: %v.", err)
  2412  		}
  2413  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
  2414  		// Call the ServeHTTP to execute the handler.
  2415  		apiRouter.ServeHTTP(recV2, reqV2)
  2416  		if recV2.Code != testCase.expectedRespStatus {
  2417  			if testCase.copySourceSame {
  2418  				// encryption will rotate creds, so fail only for non-encryption scenario.
  2419  				if GlobalKMS == nil {
  2420  					t.Errorf("Test %d: %s:  Expected the response status to be `%d`, but instead found `%d`", i, instanceType, testCase.expectedRespStatus, rec.Code)
  2421  				}
  2422  			} else {
  2423  				t.Errorf("Test %d: %s:  Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
  2424  			}
  2425  		}
  2426  	}
  2427  
  2428  	// HTTP request to test the case of `objectLayer` being set to `nil`.
  2429  	// There is no need to use an existing bucket or valid input for creating the request,
  2430  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
  2431  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
  2432  	nilBucket := "dummy-bucket"
  2433  	nilObject := "dummy-object"
  2434  
  2435  	nilReq, err := newTestSignedRequestV4(http.MethodPut, getCopyObjectURL("", nilBucket, nilObject),
  2436  		0, nil, "", "", nil)
  2437  
  2438  	// Below is how CopyObjectHandler is registered.
  2439  	// bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?")
  2440  	// Its necessary to set the "X-Amz-Copy-Source" header for the request to be accepted by the handler.
  2441  	nilReq.Header.Set("X-Amz-Copy-Source", url.QueryEscape(SlashSeparator+nilBucket+SlashSeparator+nilObject))
  2442  	if err != nil {
  2443  		t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
  2444  	}
  2445  
  2446  	// execute the object layer set to `nil` test.
  2447  	// `ExecObjectLayerAPINilTest` manages the operation.
  2448  	ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
  2449  
  2450  }
  2451  
  2452  // Wrapper for calling NewMultipartUpload tests for both Erasure multiple disks and single node setup.
  2453  // First register the HTTP handler for NewMutlipartUpload, then a HTTP request for NewMultipart upload is made.
  2454  // The UploadID from the response body is parsed and its existence is asserted with an attempt to ListParts using it.
  2455  func TestAPINewMultipartHandler(t *testing.T) {
  2456  	defer DetectTestLeak(t)()
  2457  	ExecObjectLayerAPITest(t, testAPINewMultipartHandler, []string{"NewMultipart"})
  2458  }
  2459  
  2460  func testAPINewMultipartHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
  2461  	credentials auth.Credentials, t *testing.T) {
  2462  
  2463  	objectName := "test-object-new-multipart"
  2464  	rec := httptest.NewRecorder()
  2465  	// construct HTTP request for NewMultipart upload.
  2466  	req, err := newTestSignedRequestV4(http.MethodPost, getNewMultipartURL("", bucketName, objectName),
  2467  		0, nil, credentials.AccessKey, credentials.SecretKey, nil)
  2468  
  2469  	if err != nil {
  2470  		t.Fatalf("Failed to create HTTP request for NewMultipart Request: <ERROR> %v", err)
  2471  	}
  2472  	// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
  2473  	// Call the ServeHTTP to executes the registered handler.
  2474  	apiRouter.ServeHTTP(rec, req)
  2475  	// Assert the response code with the expected status.
  2476  	if rec.Code != http.StatusOK {
  2477  		t.Fatalf("%s:  Expected the response status to be `%d`, but instead found `%d`", instanceType, http.StatusOK, rec.Code)
  2478  	}
  2479  
  2480  	// decode the response body.
  2481  	decoder := xml.NewDecoder(rec.Body)
  2482  	multipartResponse := &InitiateMultipartUploadResponse{}
  2483  
  2484  	err = decoder.Decode(multipartResponse)
  2485  	if err != nil {
  2486  		t.Fatalf("Error decoding the recorded response Body")
  2487  	}
  2488  	// verify the uploadID my making an attempt to list parts.
  2489  	_, err = obj.ListObjectParts(context.Background(), bucketName, objectName, multipartResponse.UploadID, 0, 1, ObjectOptions{})
  2490  	if err != nil {
  2491  		t.Fatalf("Invalid UploadID: <ERROR> %s", err)
  2492  	}
  2493  
  2494  	// Testing the response for Invalid AcccessID.
  2495  	// Forcing the signature check to fail.
  2496  	rec = httptest.NewRecorder()
  2497  	// construct HTTP request for NewMultipart upload.
  2498  	// Setting an invalid accessID.
  2499  	req, err = newTestSignedRequestV4(http.MethodPost, getNewMultipartURL("", bucketName, objectName),
  2500  		0, nil, "Invalid-AccessID", credentials.SecretKey, nil)
  2501  
  2502  	if err != nil {
  2503  		t.Fatalf("Failed to create HTTP request for NewMultipart Request: <ERROR> %v", err)
  2504  	}
  2505  
  2506  	// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP method to execute the logic of the handler.
  2507  	// Call the ServeHTTP to executes the registered handler.
  2508  	apiRouter.ServeHTTP(rec, req)
  2509  	// Assert the response code with the expected status.
  2510  	if rec.Code != http.StatusForbidden {
  2511  		t.Fatalf("%s:  Expected the response status to be `%d`, but instead found `%d`", instanceType, http.StatusForbidden, rec.Code)
  2512  	}
  2513  
  2514  	// Verify response of the V2 signed HTTP request.
  2515  	// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
  2516  	recV2 := httptest.NewRecorder()
  2517  	// construct HTTP request for NewMultipartUpload endpoint.
  2518  	reqV2, err := newTestSignedRequestV2(http.MethodPost, getNewMultipartURL("", bucketName, objectName),
  2519  		0, nil, credentials.AccessKey, credentials.SecretKey, nil)
  2520  
  2521  	if err != nil {
  2522  		t.Fatalf("Failed to create HTTP request for NewMultipart Request: <ERROR> %v", err)
  2523  	}
  2524  
  2525  	// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
  2526  	// Call the ServeHTTP to execute the handler.
  2527  	apiRouter.ServeHTTP(recV2, reqV2)
  2528  	// Assert the response code with the expected status.
  2529  	if recV2.Code != http.StatusOK {
  2530  		t.Fatalf("%s:  Expected the response status to be `%d`, but instead found `%d`", instanceType, http.StatusOK, recV2.Code)
  2531  	}
  2532  	// decode the response body.
  2533  	decoder = xml.NewDecoder(recV2.Body)
  2534  	multipartResponse = &InitiateMultipartUploadResponse{}
  2535  
  2536  	err = decoder.Decode(multipartResponse)
  2537  	if err != nil {
  2538  		t.Fatalf("Error decoding the recorded response Body")
  2539  	}
  2540  	// verify the uploadID my making an attempt to list parts.
  2541  	_, err = obj.ListObjectParts(context.Background(), bucketName, objectName, multipartResponse.UploadID, 0, 1, ObjectOptions{})
  2542  	if err != nil {
  2543  		t.Fatalf("Invalid UploadID: <ERROR> %s", err)
  2544  	}
  2545  
  2546  	// Testing the response for invalid AcccessID.
  2547  	// Forcing the V2 signature check to fail.
  2548  	recV2 = httptest.NewRecorder()
  2549  	// construct HTTP request for NewMultipartUpload endpoint.
  2550  	// Setting invalid AccessID.
  2551  	reqV2, err = newTestSignedRequestV2(http.MethodPost, getNewMultipartURL("", bucketName, objectName),
  2552  		0, nil, "Invalid-AccessID", credentials.SecretKey, nil)
  2553  
  2554  	if err != nil {
  2555  		t.Fatalf("Failed to create HTTP request for NewMultipart Request: <ERROR> %v", err)
  2556  	}
  2557  
  2558  	// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
  2559  	// Call the ServeHTTP to execute the handler.
  2560  	apiRouter.ServeHTTP(recV2, reqV2)
  2561  	// Assert the response code with the expected status.
  2562  	if recV2.Code != http.StatusForbidden {
  2563  		t.Fatalf("%s:  Expected the response status to be `%d`, but instead found `%d`", instanceType, http.StatusForbidden, recV2.Code)
  2564  	}
  2565  
  2566  	// Test for Anonymous/unsigned http request.
  2567  	anonReq, err := newTestRequest(http.MethodPost, getNewMultipartURL("", bucketName, objectName), 0, nil)
  2568  
  2569  	if err != nil {
  2570  		t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
  2571  			instanceType, bucketName, objectName, err)
  2572  	}
  2573  
  2574  	// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
  2575  	// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
  2576  	// unsigned request goes through and its validated again.
  2577  	ExecObjectLayerAPIAnonTest(t, obj, "TestAPINewMultipartHandler", bucketName, objectName, instanceType, apiRouter, anonReq, getAnonWriteOnlyObjectPolicy(bucketName, objectName))
  2578  
  2579  	// HTTP request to test the case of `objectLayer` being set to `nil`.
  2580  	// There is no need to use an existing bucket or valid input for creating the request,
  2581  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
  2582  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
  2583  	nilBucket := "dummy-bucket"
  2584  	nilObject := "dummy-object"
  2585  
  2586  	nilReq, err := newTestSignedRequestV4(http.MethodPost, getNewMultipartURL("", nilBucket, nilObject),
  2587  		0, nil, "", "", nil)
  2588  
  2589  	if err != nil {
  2590  		t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
  2591  	}
  2592  	// execute the object layer set to `nil` test.
  2593  	// `ExecObjectLayerAPINilTest` manages the operation.
  2594  	ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
  2595  
  2596  }
  2597  
  2598  // Wrapper for calling NewMultipartUploadParallel tests for both Erasure multiple disks and single node setup.
  2599  // The objective of the test is to initialte multipart upload on the same object 10 times concurrently,
  2600  // The UploadID from the response body is parsed and its existence is asserted with an attempt to ListParts using it.
  2601  func TestAPINewMultipartHandlerParallel(t *testing.T) {
  2602  	defer DetectTestLeak(t)()
  2603  	ExecObjectLayerAPITest(t, testAPINewMultipartHandlerParallel, []string{"NewMultipart"})
  2604  }
  2605  
  2606  func testAPINewMultipartHandlerParallel(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
  2607  	credentials auth.Credentials, t *testing.T) {
  2608  	// used for storing the uploadID's parsed on concurrent HTTP requests for NewMultipart upload on the same object.
  2609  	testUploads := struct {
  2610  		sync.Mutex
  2611  		uploads []string
  2612  	}{}
  2613  
  2614  	objectName := "test-object-new-multipart-parallel"
  2615  	var wg sync.WaitGroup
  2616  	for i := 0; i < 10; i++ {
  2617  		wg.Add(1)
  2618  		// Initiate NewMultipart upload on the same object 10 times concurrrently.
  2619  		go func() {
  2620  			defer wg.Done()
  2621  			rec := httptest.NewRecorder()
  2622  			// construct HTTP request NewMultipartUpload.
  2623  			req, err := newTestSignedRequestV4(http.MethodPost, getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKey, credentials.SecretKey, nil)
  2624  
  2625  			if err != nil {
  2626  				t.Errorf("Failed to create HTTP request for NewMultipart request: <ERROR> %v", err)
  2627  				return
  2628  			}
  2629  			// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
  2630  			// Call the ServeHTTP to executes the registered handler.
  2631  			apiRouter.ServeHTTP(rec, req)
  2632  			// Assert the response code with the expected status.
  2633  			if rec.Code != http.StatusOK {
  2634  				t.Errorf("MinIO %s:  Expected the response status to be `%d`, but instead found `%d`", instanceType, http.StatusOK, rec.Code)
  2635  				return
  2636  			}
  2637  			// decode the response body.
  2638  			decoder := xml.NewDecoder(rec.Body)
  2639  			multipartResponse := &InitiateMultipartUploadResponse{}
  2640  
  2641  			err = decoder.Decode(multipartResponse)
  2642  			if err != nil {
  2643  				t.Errorf("MinIO %s: Error decoding the recorded response Body", instanceType)
  2644  				return
  2645  			}
  2646  			// push the obtained upload ID from the response into the array.
  2647  			testUploads.Lock()
  2648  			testUploads.uploads = append(testUploads.uploads, multipartResponse.UploadID)
  2649  			testUploads.Unlock()
  2650  		}()
  2651  	}
  2652  	// Wait till all go routines finishes execution.
  2653  	wg.Wait()
  2654  	// Validate the upload ID by an attempt to list parts using it.
  2655  	for _, uploadID := range testUploads.uploads {
  2656  		_, err := obj.ListObjectParts(context.Background(), bucketName, objectName, uploadID, 0, 1, ObjectOptions{})
  2657  		if err != nil {
  2658  			t.Fatalf("Invalid UploadID: <ERROR> %s", err)
  2659  		}
  2660  	}
  2661  }
  2662  
  2663  // The UploadID from the response body is parsed and its existence is asserted with an attempt to ListParts using it.
  2664  func TestAPICompleteMultipartHandler(t *testing.T) {
  2665  	defer DetectTestLeak(t)()
  2666  	ExecObjectLayerAPITest(t, testAPICompleteMultipartHandler, []string{"CompleteMultipart"})
  2667  }
  2668  
  2669  func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
  2670  	credentials auth.Credentials, t *testing.T) {
  2671  
  2672  	var err error
  2673  
  2674  	var opts ObjectOptions
  2675  	// object used for the test.
  2676  	objectName := "test-object-new-multipart"
  2677  
  2678  	// uploadID obtained from NewMultipart upload.
  2679  	var uploadID string
  2680  	// upload IDs collected.
  2681  	var uploadIDs []string
  2682  
  2683  	for i := 0; i < 2; i++ {
  2684  		// initiate new multipart uploadID.
  2685  		uploadID, err = obj.NewMultipartUpload(context.Background(), bucketName, objectName, opts)
  2686  		if err != nil {
  2687  			// Failed to create NewMultipartUpload, abort.
  2688  			t.Fatalf("MinIO %s : <ERROR>  %s", instanceType, err)
  2689  		}
  2690  
  2691  		uploadIDs = append(uploadIDs, uploadID)
  2692  	}
  2693  
  2694  	// Parts with size greater than 5 MiB.
  2695  	// Generating a 6 MiB byte array.
  2696  	validPart := bytes.Repeat([]byte("abcdef"), 1*humanize.MiByte)
  2697  	validPartMD5 := getMD5Hash(validPart)
  2698  	// Create multipart parts.
  2699  	// Need parts to be uploaded before CompleteMultiPartUpload can be called tested.
  2700  	parts := []struct {
  2701  		bucketName      string
  2702  		objName         string
  2703  		uploadID        string
  2704  		PartID          int
  2705  		inputReaderData string
  2706  		inputMd5        string
  2707  		intputDataSize  int64
  2708  	}{
  2709  		// Case 1-4.
  2710  		// Creating sequence of parts for same uploadID.
  2711  		{bucketName, objectName, uploadIDs[0], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd"))},
  2712  		{bucketName, objectName, uploadIDs[0], 2, "efgh", "1f7690ebdd9b4caf8fab49ca1757bf27", int64(len("efgh"))},
  2713  		{bucketName, objectName, uploadIDs[0], 3, "ijkl", "09a0877d04abf8759f99adec02baf579", int64(len("abcd"))},
  2714  		{bucketName, objectName, uploadIDs[0], 4, "mnop", "e132e96a5ddad6da8b07bba6f6131fef", int64(len("abcd"))},
  2715  		// Part with size larger than 5 MiB.
  2716  		{bucketName, objectName, uploadIDs[0], 5, string(validPart), validPartMD5, int64(len(string(validPart)))},
  2717  		{bucketName, objectName, uploadIDs[0], 6, string(validPart), validPartMD5, int64(len(string(validPart)))},
  2718  
  2719  		// Part with size larger than 5 MiB.
  2720  		// Parts uploaded for anonymous/unsigned API handler test.
  2721  		{bucketName, objectName, uploadIDs[1], 1, string(validPart), validPartMD5, int64(len(string(validPart)))},
  2722  		{bucketName, objectName, uploadIDs[1], 2, string(validPart), validPartMD5, int64(len(string(validPart)))},
  2723  	}
  2724  	// Iterating over creatPartCases to generate multipart chunks.
  2725  	for _, part := range parts {
  2726  		_, err = obj.PutObjectPart(context.Background(), part.bucketName, part.objName, part.uploadID, part.PartID,
  2727  			mustGetPutObjReader(t, strings.NewReader(part.inputReaderData), part.intputDataSize, part.inputMd5, ""), opts)
  2728  		if err != nil {
  2729  			t.Fatalf("%s : %s", instanceType, err)
  2730  		}
  2731  	}
  2732  	// Parts to be sent as input for CompleteMultipartUpload.
  2733  	inputParts := []struct {
  2734  		parts []CompletePart
  2735  	}{
  2736  		// inputParts - 0.
  2737  		// Case for replicating ETag mismatch.
  2738  		{
  2739  			[]CompletePart{
  2740  				{ETag: "abcd", PartNumber: 1},
  2741  			},
  2742  		},
  2743  		// inputParts - 1.
  2744  		// should error out with part too small.
  2745  		{
  2746  			[]CompletePart{
  2747  				{ETag: "e2fc714c4727ee9395f324cd2e7f331f", PartNumber: 1},
  2748  				{ETag: "1f7690ebdd9b4caf8fab49ca1757bf27", PartNumber: 2},
  2749  			},
  2750  		},
  2751  		// inputParts - 2.
  2752  		// Case with invalid Part number.
  2753  		{
  2754  			[]CompletePart{
  2755  				{ETag: "e2fc714c4727ee9395f324cd2e7f331f", PartNumber: 10},
  2756  			},
  2757  		},
  2758  		// inputParts - 3.
  2759  		// Case with valid parts,but parts are unsorted.
  2760  		// Part size greater than 5 MiB.
  2761  		{
  2762  			[]CompletePart{
  2763  				{ETag: validPartMD5, PartNumber: 6},
  2764  				{ETag: validPartMD5, PartNumber: 5},
  2765  			},
  2766  		},
  2767  		// inputParts - 4.
  2768  		// Case with valid part.
  2769  		// Part size greater than 5 MiB.
  2770  		{
  2771  			[]CompletePart{
  2772  				{ETag: validPartMD5, PartNumber: 5},
  2773  				{ETag: validPartMD5, PartNumber: 6},
  2774  			},
  2775  		},
  2776  
  2777  		// inputParts - 5.
  2778  		// Used for the case of testing for anonymous API request.
  2779  		// Part size greater than 5 MiB.
  2780  		{
  2781  			[]CompletePart{
  2782  				{ETag: validPartMD5, PartNumber: 1},
  2783  				{ETag: validPartMD5, PartNumber: 2},
  2784  			},
  2785  		},
  2786  	}
  2787  
  2788  	// on successful complete multipart operation the s3MD5 for the parts uploaded will be returned.
  2789  	s3MD5 := getCompleteMultipartMD5(inputParts[3].parts)
  2790  
  2791  	// generating the response body content for the success case.
  2792  	successResponse := generateCompleteMultpartUploadResponse(bucketName, objectName, getGetObjectURL("", bucketName, objectName), s3MD5)
  2793  	encodedSuccessResponse := EncodeResponse(successResponse)
  2794  
  2795  	ctx := context.Background()
  2796  
  2797  	testCases := []struct {
  2798  		bucket    string
  2799  		object    string
  2800  		uploadID  string
  2801  		parts     []CompletePart
  2802  		accessKey string
  2803  		secretKey string
  2804  		// Expected output of CompleteMultipartUpload.
  2805  		expectedContent []byte
  2806  		// Expected HTTP Response status.
  2807  		expectedRespStatus int
  2808  	}{
  2809  		// Test case - 1.
  2810  		// Upload and PartNumber exists, But a deliberate ETag mismatch is introduced.
  2811  		{
  2812  			bucket:    bucketName,
  2813  			object:    objectName,
  2814  			uploadID:  uploadIDs[0],
  2815  			parts:     inputParts[0].parts,
  2816  			accessKey: credentials.AccessKey,
  2817  			secretKey: credentials.SecretKey,
  2818  
  2819  			expectedContent: EncodeResponse(getAPIErrorResponse(ctx,
  2820  				ToAPIError(ctx, InvalidPart{}),
  2821  				getGetObjectURL("", bucketName, objectName), "", "")),
  2822  			expectedRespStatus: http.StatusBadRequest,
  2823  		},
  2824  		// Test case - 2.
  2825  		// No parts specified in CompletePart{}.
  2826  		// Should return ErrMalformedXML in the response body.
  2827  		{
  2828  			bucket:    bucketName,
  2829  			object:    objectName,
  2830  			uploadID:  uploadIDs[0],
  2831  			parts:     []CompletePart{},
  2832  			accessKey: credentials.AccessKey,
  2833  			secretKey: credentials.SecretKey,
  2834  
  2835  			expectedContent: EncodeResponse(getAPIErrorResponse(ctx,
  2836  				GetAPIError(ErrMalformedXML),
  2837  				getGetObjectURL("", bucketName, objectName), "", "")),
  2838  			expectedRespStatus: http.StatusBadRequest,
  2839  		},
  2840  		// Test case - 3.
  2841  		// Non-Existent uploadID.
  2842  		// 404 Not Found response status expected.
  2843  		{
  2844  			bucket:    bucketName,
  2845  			object:    objectName,
  2846  			uploadID:  "abc",
  2847  			parts:     inputParts[0].parts,
  2848  			accessKey: credentials.AccessKey,
  2849  			secretKey: credentials.SecretKey,
  2850  
  2851  			expectedContent: EncodeResponse(getAPIErrorResponse(ctx,
  2852  				ToAPIError(ctx, InvalidUploadID{UploadID: "abc"}),
  2853  				getGetObjectURL("", bucketName, objectName), "", "")),
  2854  			expectedRespStatus: http.StatusNotFound,
  2855  		},
  2856  		// Test case - 4.
  2857  		// Case with part size being less than minimum allowed size.
  2858  		{
  2859  			bucket:    bucketName,
  2860  			object:    objectName,
  2861  			uploadID:  uploadIDs[0],
  2862  			parts:     inputParts[1].parts,
  2863  			accessKey: credentials.AccessKey,
  2864  			secretKey: credentials.SecretKey,
  2865  
  2866  			expectedContent: EncodeResponse(getAPIErrorResponse(ctx,
  2867  				ToAPIError(ctx, PartTooSmall{PartNumber: 1}),
  2868  				getGetObjectURL("", bucketName, objectName), "", "")),
  2869  			expectedRespStatus: http.StatusBadRequest,
  2870  		},
  2871  		// Test case - 5.
  2872  		// TestCase with invalid Part Number.
  2873  		{
  2874  			bucket:    bucketName,
  2875  			object:    objectName,
  2876  			uploadID:  uploadIDs[0],
  2877  			parts:     inputParts[2].parts,
  2878  			accessKey: credentials.AccessKey,
  2879  			secretKey: credentials.SecretKey,
  2880  
  2881  			expectedContent: EncodeResponse(getAPIErrorResponse(ctx,
  2882  				ToAPIError(ctx, InvalidPart{}),
  2883  				getGetObjectURL("", bucketName, objectName), "", "")),
  2884  			expectedRespStatus: http.StatusBadRequest,
  2885  		},
  2886  		// Test case - 6.
  2887  		// Parts are not sorted according to the part number.
  2888  		// This should return ErrInvalidPartOrder in the response body.
  2889  		{
  2890  			bucket:    bucketName,
  2891  			object:    objectName,
  2892  			uploadID:  uploadIDs[0],
  2893  			parts:     inputParts[3].parts,
  2894  			accessKey: credentials.AccessKey,
  2895  			secretKey: credentials.SecretKey,
  2896  
  2897  			expectedContent: EncodeResponse(getAPIErrorResponse(ctx,
  2898  				GetAPIError(ErrInvalidPartOrder),
  2899  				getGetObjectURL("", bucketName, objectName), "", "")),
  2900  			expectedRespStatus: http.StatusBadRequest,
  2901  		},
  2902  		// Test case - 7.
  2903  		// Test case with proper parts.
  2904  		// Should successed and the content in the response body is asserted.
  2905  		{
  2906  			bucket:    bucketName,
  2907  			object:    objectName,
  2908  			uploadID:  uploadIDs[0],
  2909  			parts:     inputParts[4].parts,
  2910  			accessKey: "Invalid-AccessID",
  2911  			secretKey: credentials.SecretKey,
  2912  
  2913  			expectedContent: EncodeResponse(getAPIErrorResponse(ctx,
  2914  				GetAPIError(ErrInvalidAccessKeyID),
  2915  				getGetObjectURL("", bucketName, objectName), "", "")),
  2916  			expectedRespStatus: http.StatusForbidden,
  2917  		},
  2918  		// Test case - 8.
  2919  		// Test case with proper parts.
  2920  		// Should successed and the content in the response body is asserted.
  2921  		{
  2922  			bucket:    bucketName,
  2923  			object:    objectName,
  2924  			uploadID:  uploadIDs[0],
  2925  			parts:     inputParts[4].parts,
  2926  			accessKey: credentials.AccessKey,
  2927  			secretKey: credentials.SecretKey,
  2928  
  2929  			expectedContent:    encodedSuccessResponse,
  2930  			expectedRespStatus: http.StatusOK,
  2931  		},
  2932  	}
  2933  
  2934  	for i, testCase := range testCases {
  2935  		var req *http.Request
  2936  		var completeBytes, actualContent []byte
  2937  		// Complete multipart upload parts.
  2938  		completeUploads := &CompleteMultipartUpload{
  2939  			Parts: testCase.parts,
  2940  		}
  2941  		completeBytes, err = xml.Marshal(completeUploads)
  2942  		if err != nil {
  2943  			t.Fatalf("Error XML encoding of parts: <ERROR> %s.", err)
  2944  		}
  2945  		// Indicating that all parts are uploaded and initiating CompleteMultipartUpload.
  2946  		req, err = newTestSignedRequestV4(http.MethodPost, getCompleteMultipartUploadURL("", bucketName, objectName, testCase.uploadID),
  2947  			int64(len(completeBytes)), bytes.NewReader(completeBytes), testCase.accessKey, testCase.secretKey, nil)
  2948  		if err != nil {
  2949  			t.Fatalf("Failed to create HTTP request for CompleteMultipartUpload: <ERROR> %v", err)
  2950  		}
  2951  
  2952  		rec := httptest.NewRecorder()
  2953  
  2954  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
  2955  		// Call the ServeHTTP to executes the registered handler.
  2956  		apiRouter.ServeHTTP(rec, req)
  2957  		// Assert the response code with the expected status.
  2958  		if rec.Code != testCase.expectedRespStatus {
  2959  			t.Errorf("Case %d: MinIO %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
  2960  		}
  2961  
  2962  		// read the response body.
  2963  		actualContent, err = ioutil.ReadAll(rec.Body)
  2964  		if err != nil {
  2965  			t.Fatalf("Test %d : MinIO %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
  2966  		}
  2967  
  2968  		if rec.Code == http.StatusOK {
  2969  			// Verify whether the bucket obtained object is same as the one created.
  2970  			if !bytes.Equal(testCase.expectedContent, actualContent) {
  2971  				t.Errorf("Test %d : MinIO %s: CompleteMultipart response content differs from expected value. got %s, expecte %s", i+1, instanceType,
  2972  					string(actualContent), string(testCase.expectedContent))
  2973  			}
  2974  			continue
  2975  		}
  2976  
  2977  		actualError := &APIErrorResponse{}
  2978  		if err = xml.Unmarshal(actualContent, actualError); err != nil {
  2979  			t.Errorf("MinIO %s: error response failed to parse error XML", instanceType)
  2980  		}
  2981  
  2982  		if actualError.BucketName != bucketName {
  2983  			t.Errorf("MinIO %s: error response bucket name differs from expected value", instanceType)
  2984  		}
  2985  
  2986  		if actualError.Key != objectName {
  2987  			t.Errorf("MinIO %s: error response object name (%s) differs from expected value (%s)", instanceType, actualError.Key, objectName)
  2988  		}
  2989  	}
  2990  
  2991  	// Testing for anonymous API request.
  2992  	var completeBytes []byte
  2993  	// Complete multipart upload parts.
  2994  	completeUploads := &CompleteMultipartUpload{
  2995  		Parts: inputParts[5].parts,
  2996  	}
  2997  	completeBytes, err = xml.Marshal(completeUploads)
  2998  	if err != nil {
  2999  		t.Fatalf("Error XML encoding of parts: <ERROR> %s.", err)
  3000  	}
  3001  
  3002  	// create unsigned HTTP request for CompleteMultipart upload.
  3003  	anonReq, err := newTestRequest(http.MethodPost, getCompleteMultipartUploadURL("", bucketName, objectName, uploadIDs[1]),
  3004  		int64(len(completeBytes)), bytes.NewReader(completeBytes))
  3005  	if err != nil {
  3006  		t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
  3007  			instanceType, bucketName, objectName, err)
  3008  	}
  3009  
  3010  	// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
  3011  	// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
  3012  	// unsigned request goes through and its validated again.
  3013  	ExecObjectLayerAPIAnonTest(t, obj, "TestAPICompleteMultipartHandler", bucketName, objectName, instanceType,
  3014  		apiRouter, anonReq, getAnonWriteOnlyObjectPolicy(bucketName, objectName))
  3015  
  3016  	// HTTP request to test the case of `objectLayer` being set to `nil`.
  3017  	// There is no need to use an existing bucket or valid input for creating the request,
  3018  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
  3019  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
  3020  	// Indicating that all parts are uploaded and initiating CompleteMultipartUpload.
  3021  	nilBucket := "dummy-bucket"
  3022  	nilObject := "dummy-object"
  3023  
  3024  	nilReq, err := newTestSignedRequestV4(http.MethodPost, getCompleteMultipartUploadURL("", nilBucket, nilObject, "dummy-uploadID"),
  3025  		0, nil, "", "", nil)
  3026  
  3027  	if err != nil {
  3028  		t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
  3029  	}
  3030  	// execute the object layer set to `nil` test.
  3031  	// `ExecObjectLayerAPINilTest` manages the operation.
  3032  	ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
  3033  }
  3034  
  3035  // The UploadID from the response body is parsed and its existence is asserted with an attempt to ListParts using it.
  3036  func TestAPIAbortMultipartHandler(t *testing.T) {
  3037  	defer DetectTestLeak(t)()
  3038  	ExecObjectLayerAPITest(t, testAPIAbortMultipartHandler, []string{"AbortMultipart"})
  3039  }
  3040  
  3041  func testAPIAbortMultipartHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
  3042  	credentials auth.Credentials, t *testing.T) {
  3043  
  3044  	var err error
  3045  	opts := ObjectOptions{}
  3046  	// object used for the test.
  3047  	objectName := "test-object-new-multipart"
  3048  
  3049  	// uploadID obtained from NewMultipart upload.
  3050  	var uploadID string
  3051  	// upload IDs collected.
  3052  	var uploadIDs []string
  3053  
  3054  	for i := 0; i < 2; i++ {
  3055  		// initiate new multipart uploadID.
  3056  		uploadID, err = obj.NewMultipartUpload(context.Background(), bucketName, objectName, opts)
  3057  		if err != nil {
  3058  			// Failed to create NewMultipartUpload, abort.
  3059  			t.Fatalf("MinIO %s : <ERROR>  %s", instanceType, err)
  3060  		}
  3061  
  3062  		uploadIDs = append(uploadIDs, uploadID)
  3063  	}
  3064  
  3065  	// Parts with size greater than 5 MiB.
  3066  	// Generating a 6 MiB byte array.
  3067  	validPart := bytes.Repeat([]byte("abcdef"), 1*humanize.MiByte)
  3068  	validPartMD5 := getMD5Hash(validPart)
  3069  	// Create multipart parts.
  3070  	// Need parts to be uploaded before AbortMultiPartUpload can be called tested.
  3071  	parts := []struct {
  3072  		bucketName      string
  3073  		objName         string
  3074  		uploadID        string
  3075  		PartID          int
  3076  		inputReaderData string
  3077  		inputMd5        string
  3078  		intputDataSize  int64
  3079  	}{
  3080  		// Case 1-4.
  3081  		// Creating sequence of parts for same uploadID.
  3082  		{bucketName, objectName, uploadIDs[0], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd"))},
  3083  		{bucketName, objectName, uploadIDs[0], 2, "efgh", "1f7690ebdd9b4caf8fab49ca1757bf27", int64(len("efgh"))},
  3084  		{bucketName, objectName, uploadIDs[0], 3, "ijkl", "09a0877d04abf8759f99adec02baf579", int64(len("abcd"))},
  3085  		{bucketName, objectName, uploadIDs[0], 4, "mnop", "e132e96a5ddad6da8b07bba6f6131fef", int64(len("abcd"))},
  3086  		// Part with size larger than 5 MiB.
  3087  		{bucketName, objectName, uploadIDs[0], 5, string(validPart), validPartMD5, int64(len(string(validPart)))},
  3088  		{bucketName, objectName, uploadIDs[0], 6, string(validPart), validPartMD5, int64(len(string(validPart)))},
  3089  
  3090  		// Part with size larger than 5 MiB.
  3091  		// Parts uploaded for anonymous/unsigned API handler test.
  3092  		{bucketName, objectName, uploadIDs[1], 1, string(validPart), validPartMD5, int64(len(string(validPart)))},
  3093  		{bucketName, objectName, uploadIDs[1], 2, string(validPart), validPartMD5, int64(len(string(validPart)))},
  3094  	}
  3095  	// Iterating over createPartCases to generate multipart chunks.
  3096  	for _, part := range parts {
  3097  		_, err = obj.PutObjectPart(context.Background(), part.bucketName, part.objName, part.uploadID, part.PartID,
  3098  			mustGetPutObjReader(t, strings.NewReader(part.inputReaderData), part.intputDataSize, part.inputMd5, ""), opts)
  3099  		if err != nil {
  3100  			t.Fatalf("%s : %s", instanceType, err)
  3101  		}
  3102  	}
  3103  
  3104  	testCases := []struct {
  3105  		bucket    string
  3106  		object    string
  3107  		uploadID  string
  3108  		accessKey string
  3109  		secretKey string
  3110  		// Expected HTTP Response status.
  3111  		expectedRespStatus int
  3112  	}{
  3113  		// Test case - 1.
  3114  		// Abort existing upload ID.
  3115  		{
  3116  			bucket:             bucketName,
  3117  			object:             objectName,
  3118  			uploadID:           uploadIDs[0],
  3119  			accessKey:          credentials.AccessKey,
  3120  			secretKey:          credentials.SecretKey,
  3121  			expectedRespStatus: http.StatusNoContent,
  3122  		},
  3123  		// Test case - 2.
  3124  		// Abort non-existng upload ID.
  3125  		{
  3126  			bucket:             bucketName,
  3127  			object:             objectName,
  3128  			uploadID:           "nonexistent-upload-id",
  3129  			accessKey:          credentials.AccessKey,
  3130  			secretKey:          credentials.SecretKey,
  3131  			expectedRespStatus: http.StatusNotFound,
  3132  		},
  3133  		// Test case - 3.
  3134  		// Abort with unknown Access key.
  3135  		{
  3136  			bucket:             bucketName,
  3137  			object:             objectName,
  3138  			uploadID:           uploadIDs[0],
  3139  			accessKey:          "Invalid-AccessID",
  3140  			secretKey:          credentials.SecretKey,
  3141  			expectedRespStatus: http.StatusForbidden,
  3142  		},
  3143  	}
  3144  
  3145  	for i, testCase := range testCases {
  3146  		var req *http.Request
  3147  		// Indicating that all parts are uploaded and initiating abortMultipartUpload.
  3148  		req, err = newTestSignedRequestV4(http.MethodDelete, getAbortMultipartUploadURL("", testCase.bucket, testCase.object, testCase.uploadID),
  3149  			0, nil, testCase.accessKey, testCase.secretKey, nil)
  3150  		if err != nil {
  3151  			t.Fatalf("Failed to create HTTP request for AbortMultipartUpload: <ERROR> %v", err)
  3152  		}
  3153  
  3154  		rec := httptest.NewRecorder()
  3155  
  3156  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
  3157  		// Call the ServeHTTP to executes the registered handler.
  3158  		apiRouter.ServeHTTP(rec, req)
  3159  		// Assert the response code with the expected status.
  3160  		if rec.Code != testCase.expectedRespStatus {
  3161  			t.Errorf("Case %d: MinIO %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
  3162  		}
  3163  	}
  3164  
  3165  	// create unsigned HTTP request for Abort multipart upload.
  3166  	anonReq, err := newTestRequest(http.MethodDelete, getAbortMultipartUploadURL("", bucketName, objectName, uploadIDs[1]),
  3167  		0, nil)
  3168  	if err != nil {
  3169  		t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
  3170  			instanceType, bucketName, objectName, err)
  3171  	}
  3172  
  3173  	// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
  3174  	// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
  3175  	// unsigned request goes through and its validated again.
  3176  	ExecObjectLayerAPIAnonTest(t, obj, "TestAPIAbortMultipartHandler", bucketName, objectName, instanceType,
  3177  		apiRouter, anonReq, getAnonWriteOnlyObjectPolicy(bucketName, objectName))
  3178  
  3179  	// HTTP request to test the case of `objectLayer` being set to `nil`.
  3180  	// There is no need to use an existing bucket or valid input for creating the request,
  3181  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
  3182  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
  3183  	// Indicating that all parts are uploaded and initiating abortMultipartUpload.
  3184  	nilBucket := "dummy-bucket"
  3185  	nilObject := "dummy-object"
  3186  
  3187  	nilReq, err := newTestSignedRequestV4(http.MethodDelete, getAbortMultipartUploadURL("", nilBucket, nilObject, "dummy-uploadID"),
  3188  		0, nil, "", "", nil)
  3189  
  3190  	if err != nil {
  3191  		t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
  3192  	}
  3193  	// execute the object layer set to `nil` test.
  3194  	// `ExecObjectLayerAPINilTest` manages the operation.
  3195  	ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
  3196  }
  3197  
  3198  // Wrapper for calling Delete Object API handler tests for both Erasure multiple disks and FS single drive setup.
  3199  func TestAPIDeleteObjectHandler(t *testing.T) {
  3200  	defer DetectTestLeak(t)()
  3201  	ExecObjectLayerAPITest(t, testAPIDeleteObjectHandler, []string{"DeleteObject"})
  3202  }
  3203  
  3204  func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
  3205  	credentials auth.Credentials, t *testing.T) {
  3206  
  3207  	var err error
  3208  	objectName := "test-object"
  3209  	// Object used for anonymous API request test.
  3210  	anonObjectName := "test-anon-obj"
  3211  	// set of byte data for PutObject.
  3212  	// object has to be created before running tests for Deleting the object.
  3213  	bytesData := []struct {
  3214  		byteData []byte
  3215  	}{
  3216  		{generateBytesData(6 * humanize.MiByte)},
  3217  	}
  3218  
  3219  	// set of inputs for uploading the objects before tests for deleting them is done.
  3220  	putObjectInputs := []struct {
  3221  		bucketName    string
  3222  		objectName    string
  3223  		contentLength int64
  3224  		textData      []byte
  3225  		metaData      map[string]string
  3226  	}{
  3227  		// case - 1.
  3228  		{bucketName, objectName, int64(len(bytesData[0].byteData)), bytesData[0].byteData, make(map[string]string)},
  3229  		// case - 2.
  3230  		{bucketName, anonObjectName, int64(len(bytesData[0].byteData)), bytesData[0].byteData, make(map[string]string)},
  3231  	}
  3232  	// iterate through the above set of inputs and upload the object.
  3233  	for i, input := range putObjectInputs {
  3234  		// uploading the object.
  3235  		_, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewReader(input.textData), input.contentLength, input.metaData[""], ""), ObjectOptions{UserDefined: input.metaData})
  3236  		// if object upload fails stop the test.
  3237  		if err != nil {
  3238  			t.Fatalf("Put Object case %d:  Error uploading object: <ERROR> %v", i+1, err)
  3239  		}
  3240  	}
  3241  
  3242  	// test cases with inputs and expected result for DeleteObject.
  3243  	testCases := []struct {
  3244  		bucketName string
  3245  		objectName string
  3246  		accessKey  string
  3247  		secretKey  string
  3248  
  3249  		expectedRespStatus int // expected response status body.
  3250  	}{
  3251  		// Test case - 1.
  3252  		// Deleting an existing object.
  3253  		// Expected to return HTTP resposne status code 204.
  3254  		{
  3255  			bucketName: bucketName,
  3256  			objectName: objectName,
  3257  			accessKey:  credentials.AccessKey,
  3258  			secretKey:  credentials.SecretKey,
  3259  
  3260  			expectedRespStatus: http.StatusNoContent,
  3261  		},
  3262  		// Test case - 2.
  3263  		// Attempt to delete an object which is already deleted.
  3264  		// Still should return http response status 204.
  3265  		{
  3266  			bucketName: bucketName,
  3267  			objectName: objectName,
  3268  			accessKey:  credentials.AccessKey,
  3269  			secretKey:  credentials.SecretKey,
  3270  
  3271  			expectedRespStatus: http.StatusNoContent,
  3272  		},
  3273  		// Test case - 3.
  3274  		// Setting Invalid AccessKey to force signature check inside the handler to fail.
  3275  		// Should return HTTP response status 403 forbidden.
  3276  		{
  3277  			bucketName: bucketName,
  3278  			objectName: objectName,
  3279  			accessKey:  "Invalid-AccessKey",
  3280  			secretKey:  credentials.SecretKey,
  3281  
  3282  			expectedRespStatus: http.StatusForbidden,
  3283  		},
  3284  	}
  3285  
  3286  	// Iterating over the cases, call DeleteObjectHandler and validate the HTTP response.
  3287  	for i, testCase := range testCases {
  3288  		var req, reqV2 *http.Request
  3289  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
  3290  		rec := httptest.NewRecorder()
  3291  		// construct HTTP request for Delete Object end point.
  3292  		req, err = newTestSignedRequestV4(http.MethodDelete, getDeleteObjectURL("", testCase.bucketName, testCase.objectName),
  3293  			0, nil, testCase.accessKey, testCase.secretKey, nil)
  3294  
  3295  		if err != nil {
  3296  			t.Fatalf("Test %d: Failed to create HTTP request for Delete Object: <ERROR> %v", i+1, err)
  3297  		}
  3298  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
  3299  		// Call the ServeHTTP to execute the handler,`func (api ObjectAPIHandlers) DeleteObjectHandler`  handles the request.
  3300  		apiRouter.ServeHTTP(rec, req)
  3301  		// Assert the response code with the expected status.
  3302  		if rec.Code != testCase.expectedRespStatus {
  3303  			t.Fatalf("MinIO %s: Case %d: Expected the response status to be `%d`, but instead found `%d`", instanceType, i+1, testCase.expectedRespStatus, rec.Code)
  3304  		}
  3305  
  3306  		// Verify response of the V2 signed HTTP request.
  3307  		// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
  3308  		recV2 := httptest.NewRecorder()
  3309  		// construct HTTP request for Delete Object endpoint.
  3310  		reqV2, err = newTestSignedRequestV2(http.MethodDelete, getDeleteObjectURL("", testCase.bucketName, testCase.objectName),
  3311  			0, nil, testCase.accessKey, testCase.secretKey, nil)
  3312  
  3313  		if err != nil {
  3314  			t.Fatalf("Failed to create HTTP request for NewMultipart Request: <ERROR> %v", err)
  3315  		}
  3316  
  3317  		// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
  3318  		// Call the ServeHTTP to execute the handler.
  3319  		apiRouter.ServeHTTP(recV2, reqV2)
  3320  		// Assert the response code with the expected status.
  3321  		if recV2.Code != testCase.expectedRespStatus {
  3322  			t.Errorf("Case %d: MinIO %s: Expected the response status to be `%d`, but instead found `%d`", i+1,
  3323  				instanceType, testCase.expectedRespStatus, recV2.Code)
  3324  		}
  3325  
  3326  	}
  3327  
  3328  	// Test for Anonymous/unsigned http request.
  3329  	anonReq, err := newTestRequest(http.MethodDelete, getDeleteObjectURL("", bucketName, anonObjectName), 0, nil)
  3330  	if err != nil {
  3331  		t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
  3332  			instanceType, bucketName, anonObjectName, err)
  3333  	}
  3334  
  3335  	// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
  3336  	// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
  3337  	// unsigned request goes through and its validated again.
  3338  	ExecObjectLayerAPIAnonTest(t, obj, "TestAPIDeleteObjectHandler", bucketName, anonObjectName, instanceType, apiRouter, anonReq, getAnonWriteOnlyObjectPolicy(bucketName, anonObjectName))
  3339  
  3340  	// HTTP request to test the case of `objectLayer` being set to `nil`.
  3341  	// There is no need to use an existing bucket or valid input for creating the request,
  3342  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
  3343  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
  3344  	nilBucket := "dummy-bucket"
  3345  	nilObject := "dummy-object"
  3346  
  3347  	nilReq, err := newTestSignedRequestV4(http.MethodDelete, getDeleteObjectURL("", nilBucket, nilObject),
  3348  		0, nil, "", "", nil)
  3349  
  3350  	if err != nil {
  3351  		t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType)
  3352  	}
  3353  	// execute the object layer set to `nil` test.
  3354  	// `ExecObjectLayerAPINilTest` manages the operation.
  3355  	ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
  3356  }
  3357  
  3358  // TestAPIPutObjectPartHandlerStreaming - Tests validate the response of PutObjectPart HTTP handler
  3359  // when the request signature type is `streaming signature`.
  3360  func TestAPIPutObjectPartHandlerStreaming(t *testing.T) {
  3361  	defer DetectTestLeak(t)()
  3362  	ExecExtendedObjectLayerAPITest(t, testAPIPutObjectPartHandlerStreaming, []string{"NewMultipart", "PutObjectPart"})
  3363  }
  3364  
  3365  func testAPIPutObjectPartHandlerStreaming(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
  3366  	credentials auth.Credentials, t *testing.T) {
  3367  	testObject := "testobject"
  3368  	rec := httptest.NewRecorder()
  3369  	req, err := newTestSignedRequestV4(http.MethodPost, getNewMultipartURL("", bucketName, "testobject"),
  3370  		0, nil, credentials.AccessKey, credentials.SecretKey, nil)
  3371  	if err != nil {
  3372  		t.Fatalf("[%s] - Failed to create a signed request to initiate multipart upload for %s/%s: <ERROR> %v",
  3373  			instanceType, bucketName, testObject, err)
  3374  	}
  3375  	apiRouter.ServeHTTP(rec, req)
  3376  
  3377  	// Get uploadID of the mulitpart upload initiated.
  3378  	var mpartResp InitiateMultipartUploadResponse
  3379  	mpartRespBytes, err := ioutil.ReadAll(rec.Result().Body)
  3380  	if err != nil {
  3381  		t.Fatalf("[%s] Failed to read NewMultipartUpload response <ERROR> %v", instanceType, err)
  3382  
  3383  	}
  3384  	err = xml.Unmarshal(mpartRespBytes, &mpartResp)
  3385  	if err != nil {
  3386  		t.Fatalf("[%s] Failed to unmarshal NewMultipartUpload response <ERROR> %v", instanceType, err)
  3387  	}
  3388  
  3389  	noAPIErr := APIError{}
  3390  	missingDateHeaderErr := GetAPIError(ErrMissingDateHeader)
  3391  	internalErr := GetAPIError(ErrInternalError)
  3392  	testCases := []struct {
  3393  		fault       Fault
  3394  		expectedErr APIError
  3395  	}{
  3396  		{BadSignature, missingDateHeaderErr},
  3397  		{None, noAPIErr},
  3398  		{TooBigDecodedLength, internalErr},
  3399  	}
  3400  
  3401  	for i, test := range testCases {
  3402  		rec = httptest.NewRecorder()
  3403  		req, err = newTestStreamingSignedRequest(http.MethodPut,
  3404  			getPutObjectPartURL("", bucketName, testObject, mpartResp.UploadID, "1"),
  3405  			5, 1, bytes.NewReader([]byte("hello")), credentials.AccessKey, credentials.SecretKey)
  3406  
  3407  		if err != nil {
  3408  			t.Fatalf("Failed to create new streaming signed HTTP request: <ERROR> %v.", err)
  3409  		}
  3410  		switch test.fault {
  3411  		case BadSignature:
  3412  			// Reset date field in header to make streaming signature fail.
  3413  			req.Header.Set("x-amz-date", "")
  3414  		case TooBigDecodedLength:
  3415  			// Set decoded length to a large value out of int64 range to simulate parse failure.
  3416  			req.Header.Set("x-amz-decoded-content-length", "9999999999999999999999")
  3417  		}
  3418  		apiRouter.ServeHTTP(rec, req)
  3419  
  3420  		if test.expectedErr != noAPIErr {
  3421  			errBytes, err := ioutil.ReadAll(rec.Result().Body)
  3422  			if err != nil {
  3423  				t.Fatalf("Test %d %s Failed to read error response from upload part request %s/%s: <ERROR> %v",
  3424  					i+1, instanceType, bucketName, testObject, err)
  3425  			}
  3426  			var errXML APIErrorResponse
  3427  			err = xml.Unmarshal(errBytes, &errXML)
  3428  			if err != nil {
  3429  				t.Fatalf("Test %d %s Failed to unmarshal error response from upload part request %s/%s: <ERROR> %v",
  3430  					i+1, instanceType, bucketName, testObject, err)
  3431  			}
  3432  			if test.expectedErr.Code != errXML.Code {
  3433  				t.Errorf("Test %d %s expected to fail with error %s, but received %s", i+1, instanceType,
  3434  					test.expectedErr.Code, errXML.Code)
  3435  			}
  3436  		} else {
  3437  			if rec.Code != http.StatusOK {
  3438  				t.Errorf("Test %d %s expected to succeed, but failed with HTTP status code %d",
  3439  					i+1, instanceType, rec.Code)
  3440  
  3441  			}
  3442  		}
  3443  	}
  3444  }
  3445  
  3446  // TestAPIPutObjectPartHandler - Tests validate the response of PutObjectPart HTTP handler
  3447  //  for variety of inputs.
  3448  func TestAPIPutObjectPartHandler(t *testing.T) {
  3449  	defer DetectTestLeak(t)()
  3450  	ExecExtendedObjectLayerAPITest(t, testAPIPutObjectPartHandler, []string{"PutObjectPart"})
  3451  }
  3452  
  3453  func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
  3454  	credentials auth.Credentials, t *testing.T) {
  3455  
  3456  	// Initiate Multipart upload for testing PutObjectPartHandler.
  3457  	testObject := "testobject"
  3458  	var opts ObjectOptions
  3459  	// PutObjectPart API HTTP Handler has to be tested in isolation,
  3460  	// that is without any other handler being registered,
  3461  	// That's why NewMultipartUpload is initiated using ObjectLayer.
  3462  	uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
  3463  	if err != nil {
  3464  		// Failed to create NewMultipartUpload, abort.
  3465  		t.Fatalf("MinIO %s : <ERROR>  %s", instanceType, err)
  3466  	}
  3467  
  3468  	uploadIDCopy := uploadID
  3469  
  3470  	// expected error types for invalid inputs to PutObjectPartHandler.
  3471  	noAPIErr := APIError{}
  3472  	// expected error when content length is missing in the HTTP request.
  3473  	missingContent := GetAPIError(ErrMissingContentLength)
  3474  	// expected error when content length is too large.
  3475  	entityTooLarge := GetAPIError(ErrEntityTooLarge)
  3476  	// expected error when the signature check fails.
  3477  	badSigning := GetAPIError(ErrSignatureDoesNotMatch)
  3478  	// expected error MD5 sum mismatch occurs.
  3479  	badChecksum := GetAPIError(ErrInvalidDigest)
  3480  	// expected error when the part number in the request is invalid.
  3481  	invalidPart := GetAPIError(ErrInvalidPart)
  3482  	// expected error when maxPart is beyond the limit.
  3483  	invalidMaxParts := GetAPIError(ErrInvalidMaxParts)
  3484  	// expected error the when the uploadID is invalid.
  3485  	noSuchUploadID := GetAPIError(ErrNoSuchUpload)
  3486  	// expected error when InvalidAccessID is set.
  3487  	invalidAccessID := GetAPIError(ErrInvalidAccessKeyID)
  3488  
  3489  	// SignatureMismatch for various signing types
  3490  	testCases := []struct {
  3491  		objectName string
  3492  		reader     io.ReadSeeker
  3493  		partNumber string
  3494  		fault      Fault
  3495  		accessKey  string
  3496  		secretKey  string
  3497  
  3498  		expectedAPIError APIError
  3499  	}{
  3500  		// Test case - 1.
  3501  		// Success case.
  3502  		{
  3503  			objectName: testObject,
  3504  			reader:     bytes.NewReader([]byte("hello")),
  3505  			partNumber: "1",
  3506  			fault:      None,
  3507  			accessKey:  credentials.AccessKey,
  3508  			secretKey:  credentials.SecretKey,
  3509  
  3510  			expectedAPIError: noAPIErr,
  3511  		},
  3512  		// Test case - 2.
  3513  		// Case where part number is invalid.
  3514  		{
  3515  			objectName: testObject,
  3516  			reader:     bytes.NewReader([]byte("hello")),
  3517  			partNumber: "9999999999999999999",
  3518  			fault:      None,
  3519  			accessKey:  credentials.AccessKey,
  3520  			secretKey:  credentials.SecretKey,
  3521  
  3522  			expectedAPIError: invalidPart,
  3523  		},
  3524  		// Test case - 3.
  3525  		// Case where the part number has exceeded the max allowed parts in an upload.
  3526  		{
  3527  			objectName: testObject,
  3528  			reader:     bytes.NewReader([]byte("hello")),
  3529  			partNumber: strconv.Itoa(globalMaxPartID + 1),
  3530  			fault:      None,
  3531  			accessKey:  credentials.AccessKey,
  3532  			secretKey:  credentials.SecretKey,
  3533  
  3534  			expectedAPIError: invalidMaxParts,
  3535  		},
  3536  		// Test case - 4.
  3537  		// Case where the content length is not set in the HTTP request.
  3538  		{
  3539  			objectName: testObject,
  3540  			reader:     bytes.NewReader([]byte("hello")),
  3541  			partNumber: "1",
  3542  			fault:      MissingContentLength,
  3543  			accessKey:  credentials.AccessKey,
  3544  			secretKey:  credentials.SecretKey,
  3545  
  3546  			expectedAPIError: missingContent,
  3547  		},
  3548  		// Test case - 5.
  3549  		// case where the object size is set to a value greater than the max allowed size.
  3550  		{
  3551  			objectName: testObject,
  3552  			reader:     bytes.NewReader([]byte("hello")),
  3553  			partNumber: "1",
  3554  			fault:      TooBigObject,
  3555  			accessKey:  credentials.AccessKey,
  3556  			secretKey:  credentials.SecretKey,
  3557  
  3558  			expectedAPIError: entityTooLarge,
  3559  		},
  3560  		// Test case - 6.
  3561  		// case where a signature mismatch is introduced and the response is validated.
  3562  		{
  3563  			objectName: testObject,
  3564  			reader:     bytes.NewReader([]byte("hello")),
  3565  			partNumber: "1",
  3566  			fault:      BadSignature,
  3567  			accessKey:  credentials.AccessKey,
  3568  			secretKey:  credentials.SecretKey,
  3569  
  3570  			expectedAPIError: badSigning,
  3571  		},
  3572  		// Test case - 7.
  3573  		// Case where incorrect checksum is set and the error response
  3574  		// is asserted with the expected error response.
  3575  		{
  3576  			objectName: testObject,
  3577  			reader:     bytes.NewReader([]byte("hello")),
  3578  			partNumber: "1",
  3579  			fault:      BadMD5,
  3580  			accessKey:  credentials.AccessKey,
  3581  			secretKey:  credentials.SecretKey,
  3582  
  3583  			expectedAPIError: badChecksum,
  3584  		},
  3585  		// Test case - 8.
  3586  		// case where the a non-existent uploadID is set.
  3587  		{
  3588  			objectName: testObject,
  3589  			reader:     bytes.NewReader([]byte("hello")),
  3590  			partNumber: "1",
  3591  			fault:      MissingUploadID,
  3592  			accessKey:  credentials.AccessKey,
  3593  			secretKey:  credentials.SecretKey,
  3594  
  3595  			expectedAPIError: noSuchUploadID,
  3596  		},
  3597  		// Test case - 9.
  3598  		// case with invalid AccessID.
  3599  		// Forcing the signature check inside the handler to fail.
  3600  		{
  3601  			objectName: testObject,
  3602  			reader:     bytes.NewReader([]byte("hello")),
  3603  			partNumber: "1",
  3604  			fault:      None,
  3605  			accessKey:  "Invalid-AccessID",
  3606  			secretKey:  credentials.SecretKey,
  3607  
  3608  			expectedAPIError: invalidAccessID,
  3609  		},
  3610  	}
  3611  
  3612  	reqV2Str := "V2 Signed HTTP request"
  3613  	reqV4Str := "V4 Signed HTTP request"
  3614  
  3615  	// collection of input HTTP request, ResponseRecorder and request type.
  3616  	// Used to make a collection of V4 and V4 HTTP request.
  3617  	type inputReqRec struct {
  3618  		req     *http.Request
  3619  		rec     *httptest.ResponseRecorder
  3620  		reqType string
  3621  	}
  3622  
  3623  	for i, test := range testCases {
  3624  		// Using sub-tests introduced in Go 1.7.
  3625  		t.Run(fmt.Sprintf("MinIO %s : Test case %d.", instanceType, i+1), func(t *testing.T) {
  3626  
  3627  			var reqV4, reqV2 *http.Request
  3628  			var recV4, recV2 *httptest.ResponseRecorder
  3629  
  3630  			// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
  3631  			recV4 = httptest.NewRecorder()
  3632  			recV2 = httptest.NewRecorder()
  3633  			// setting a non-existent uploadID.
  3634  			// deliberately introducing the invalid value to be able to assert the response with the expected error response.
  3635  			if test.fault == MissingUploadID {
  3636  				uploadID = "upload1"
  3637  			}
  3638  			// constructing a v4 signed HTTP request.
  3639  			reqV4, err = newTestSignedRequestV4(http.MethodPut,
  3640  				getPutObjectPartURL("", bucketName, test.objectName, uploadID, test.partNumber),
  3641  				0, test.reader, test.accessKey, test.secretKey, nil)
  3642  			if err != nil {
  3643  				t.Fatalf("Failed to create a signed V4 request to upload part for %s/%s: <ERROR> %v",
  3644  					bucketName, test.objectName, err)
  3645  			}
  3646  			// Verify response of the V2 signed HTTP request.
  3647  			// construct HTTP request for PutObject Part Object endpoint.
  3648  			reqV2, err = newTestSignedRequestV2(http.MethodPut,
  3649  				getPutObjectPartURL("", bucketName, test.objectName, uploadID, test.partNumber),
  3650  				0, test.reader, test.accessKey, test.secretKey, nil)
  3651  
  3652  			if err != nil {
  3653  				t.Fatalf("Test %d %s Failed to create a V2  signed request to upload part for %s/%s: <ERROR> %v", i+1, instanceType,
  3654  					bucketName, test.objectName, err)
  3655  			}
  3656  
  3657  			// collection of input HTTP request, ResponseRecorder and request type.
  3658  			reqRecs := []inputReqRec{
  3659  				{
  3660  					req:     reqV4,
  3661  					rec:     recV4,
  3662  					reqType: reqV4Str,
  3663  				},
  3664  				{
  3665  					req:     reqV2,
  3666  					rec:     recV2,
  3667  					reqType: reqV2Str,
  3668  				},
  3669  			}
  3670  
  3671  			for _, reqRec := range reqRecs {
  3672  				// Response recorder to record the response of the handler.
  3673  				rec := reqRec.rec
  3674  				// HTTP request used to call the handler.
  3675  				req := reqRec.req
  3676  				// HTTP request type string for V4/V2 requests.
  3677  				reqType := reqRec.reqType
  3678  
  3679  				// introduce faults in the request.
  3680  				// deliberately introducing the invalid value to be able to assert the response with the expected error response.
  3681  				switch test.fault {
  3682  				case MissingContentLength:
  3683  					req.ContentLength = -1
  3684  					// Setting the content length to a value greater than the max allowed size of a part.
  3685  					// Used in test case  4.
  3686  				case TooBigObject:
  3687  					req.ContentLength = globalMaxObjectSize + 1
  3688  					// Malformed signature.
  3689  					// Used in test case  6.
  3690  				case BadSignature:
  3691  					req.Header.Set("authorization", req.Header.Get("authorization")+"a")
  3692  					// Setting an invalid Content-MD5 to force a Md5 Mismatch error.
  3693  					// Used in tesr case 7.
  3694  				case BadMD5:
  3695  					req.Header.Set("Content-MD5", "badmd5")
  3696  				}
  3697  
  3698  				// invoke the PutObjectPart HTTP handler.
  3699  				apiRouter.ServeHTTP(rec, req)
  3700  
  3701  				// validate the error response.
  3702  				if test.expectedAPIError != noAPIErr {
  3703  					var errBytes []byte
  3704  					// read the response body.
  3705  					errBytes, err = ioutil.ReadAll(rec.Result().Body)
  3706  					if err != nil {
  3707  						t.Fatalf("%s, Failed to read error response from upload part request \"%s\"/\"%s\": <ERROR> %v.",
  3708  							reqType, bucketName, test.objectName, err)
  3709  					}
  3710  					// parse the XML error response.
  3711  					var errXML APIErrorResponse
  3712  					err = xml.Unmarshal(errBytes, &errXML)
  3713  					if err != nil {
  3714  						t.Fatalf("%s, Failed to unmarshal error response from upload part request \"%s\"/\"%s\": <ERROR> %v.",
  3715  							reqType, bucketName, test.objectName, err)
  3716  					}
  3717  					// Validate whether the error has occurred for the expected reason.
  3718  					if test.expectedAPIError.Code != errXML.Code {
  3719  						t.Errorf("%s, Expected to fail with error \"%s\", but received \"%s\".",
  3720  							reqType, test.expectedAPIError.Code, errXML.Code)
  3721  					}
  3722  					// Validate the HTTP response status code  with the expected one.
  3723  					if test.expectedAPIError.HTTPStatusCode != rec.Code {
  3724  						t.Errorf("%s, Expected the HTTP response status code to be %d, got %d.", reqType, test.expectedAPIError.HTTPStatusCode, rec.Code)
  3725  					}
  3726  				}
  3727  			}
  3728  		})
  3729  	}
  3730  
  3731  	// Test for Anonymous/unsigned http request.
  3732  	anonReq, err := newTestRequest(http.MethodPut, getPutObjectPartURL("", bucketName, testObject, uploadIDCopy, "1"),
  3733  		int64(len("hello")), bytes.NewReader([]byte("hello")))
  3734  	if err != nil {
  3735  		t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
  3736  			instanceType, bucketName, testObject, err)
  3737  	}
  3738  
  3739  	// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
  3740  	// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
  3741  	// unsigned request goes through and its validated again.
  3742  	ExecObjectLayerAPIAnonTest(t, obj, "TestAPIPutObjectPartHandler", bucketName, testObject, instanceType, apiRouter, anonReq, getAnonWriteOnlyObjectPolicy(bucketName, testObject))
  3743  
  3744  	// HTTP request for testing when `ObjectLayer` is set to `nil`.
  3745  	// There is no need to use an existing bucket and valid input for creating the request
  3746  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
  3747  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
  3748  	nilBucket := "dummy-bucket"
  3749  	nilObject := "dummy-object"
  3750  
  3751  	nilReq, err := newTestSignedRequestV4(http.MethodPut, getPutObjectPartURL("", nilBucket, nilObject, "0", "0"),
  3752  		0, bytes.NewReader([]byte("testNilObjLayer")), "", "", nil)
  3753  
  3754  	if err != nil {
  3755  		t.Errorf("MinIO %s: Failed to create http request for testing the response when object Layer is set to `nil`.", instanceType)
  3756  	}
  3757  	// execute the object layer set to `nil` test.
  3758  	// `ExecObjectLayerAPINilTest` manages the operation.
  3759  	ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
  3760  }
  3761  
  3762  // TestAPIListObjectPartsHandlerPreSign - Tests validate the response of ListObjectParts HTTP handler
  3763  //  when signature type of the HTTP request is `Presigned`.
  3764  func TestAPIListObjectPartsHandlerPreSign(t *testing.T) {
  3765  	defer DetectTestLeak(t)()
  3766  	ExecObjectLayerAPITest(t, testAPIListObjectPartsHandlerPreSign,
  3767  		[]string{"PutObjectPart", "NewMultipart", "ListObjectParts"})
  3768  }
  3769  
  3770  func testAPIListObjectPartsHandlerPreSign(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
  3771  	credentials auth.Credentials, t *testing.T) {
  3772  	testObject := "testobject"
  3773  	rec := httptest.NewRecorder()
  3774  	req, err := newTestSignedRequestV4(http.MethodPost, getNewMultipartURL("", bucketName, testObject),
  3775  		0, nil, credentials.AccessKey, credentials.SecretKey, nil)
  3776  	if err != nil {
  3777  		t.Fatalf("[%s] - Failed to create a signed request to initiate multipart upload for %s/%s: <ERROR> %v",
  3778  			instanceType, bucketName, testObject, err)
  3779  	}
  3780  	apiRouter.ServeHTTP(rec, req)
  3781  
  3782  	// Get uploadID of the mulitpart upload initiated.
  3783  	var mpartResp InitiateMultipartUploadResponse
  3784  	mpartRespBytes, err := ioutil.ReadAll(rec.Result().Body)
  3785  	if err != nil {
  3786  		t.Fatalf("[%s] Failed to read NewMultipartUpload response <ERROR> %v", instanceType, err)
  3787  
  3788  	}
  3789  	err = xml.Unmarshal(mpartRespBytes, &mpartResp)
  3790  	if err != nil {
  3791  		t.Fatalf("[%s] Failed to unmarshal NewMultipartUpload response <ERROR> %v", instanceType, err)
  3792  	}
  3793  
  3794  	// Upload a part for listing purposes.
  3795  	rec = httptest.NewRecorder()
  3796  	req, err = newTestSignedRequestV4(http.MethodPut,
  3797  		getPutObjectPartURL("", bucketName, testObject, mpartResp.UploadID, "1"),
  3798  		int64(len("hello")), bytes.NewReader([]byte("hello")), credentials.AccessKey, credentials.SecretKey, nil)
  3799  	if err != nil {
  3800  		t.Fatalf("[%s] - Failed to create a signed request to initiate multipart upload for %s/%s: <ERROR> %v",
  3801  			instanceType, bucketName, testObject, err)
  3802  	}
  3803  	apiRouter.ServeHTTP(rec, req)
  3804  
  3805  	if rec.Code != http.StatusOK {
  3806  		t.Fatalf("[%s] - Failed to PutObjectPart bucket: %s object: %s HTTP status code: %d",
  3807  			instanceType, bucketName, testObject, rec.Code)
  3808  	}
  3809  	rec = httptest.NewRecorder()
  3810  	req, err = newTestRequest(http.MethodGet,
  3811  		getListMultipartURLWithParams("", bucketName, testObject, mpartResp.UploadID, "", "", ""),
  3812  		0, nil)
  3813  	if err != nil {
  3814  		t.Fatalf("[%s] - Failed to create an unsigned request to list object parts for bucket %s, uploadId %s",
  3815  			instanceType, bucketName, mpartResp.UploadID)
  3816  	}
  3817  
  3818  	req.Header = http.Header{}
  3819  	err = preSignV2(req, credentials.AccessKey, credentials.SecretKey, int64(10*60*60))
  3820  	if err != nil {
  3821  		t.Fatalf("[%s] - Failed to presignV2 an unsigned request to list object parts for bucket %s, uploadId %s",
  3822  			instanceType, bucketName, mpartResp.UploadID)
  3823  	}
  3824  	apiRouter.ServeHTTP(rec, req)
  3825  	if rec.Code != http.StatusOK {
  3826  		t.Errorf("Test %d %s expected to succeed but failed with HTTP status code %d",
  3827  			1, instanceType, rec.Code)
  3828  	}
  3829  
  3830  	rec = httptest.NewRecorder()
  3831  	req, err = newTestRequest(http.MethodGet,
  3832  		getListMultipartURLWithParams("", bucketName, testObject, mpartResp.UploadID, "", "", ""),
  3833  		0, nil)
  3834  	if err != nil {
  3835  		t.Fatalf("[%s] - Failed to create an unsigned request to list object parts for bucket %s, uploadId %s",
  3836  			instanceType, bucketName, mpartResp.UploadID)
  3837  	}
  3838  
  3839  	err = preSignV4(req, credentials.AccessKey, credentials.SecretKey, int64(10*60*60))
  3840  	if err != nil {
  3841  		t.Fatalf("[%s] - Failed to presignV2 an unsigned request to list object parts for bucket %s, uploadId %s",
  3842  			instanceType, bucketName, mpartResp.UploadID)
  3843  	}
  3844  	apiRouter.ServeHTTP(rec, req)
  3845  	if rec.Code != http.StatusOK {
  3846  		t.Errorf("Test %d %s expected to succeed but failed with HTTP status code %d",
  3847  			1, instanceType, rec.Code)
  3848  	}
  3849  }
  3850  
  3851  // TestAPIListObjectPartsHandler - Tests validate the response of ListObjectParts HTTP handler
  3852  //  for variety of success/failure cases.
  3853  func TestAPIListObjectPartsHandler(t *testing.T) {
  3854  	defer DetectTestLeak(t)()
  3855  	ExecExtendedObjectLayerAPITest(t, testAPIListObjectPartsHandler, []string{"ListObjectParts"})
  3856  }
  3857  
  3858  func testAPIListObjectPartsHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
  3859  	credentials auth.Credentials, t *testing.T) {
  3860  	testObject := "testobject"
  3861  	var opts ObjectOptions
  3862  	// PutObjectPart API HTTP Handler has to be tested in isolation,
  3863  	// that is without any other handler being registered,
  3864  	// That's why NewMultipartUpload is initiated using ObjectLayer.
  3865  	uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
  3866  	if err != nil {
  3867  		// Failed to create NewMultipartUpload, abort.
  3868  		t.Fatalf("MinIO %s : <ERROR>  %s", instanceType, err)
  3869  	}
  3870  
  3871  	uploadIDCopy := uploadID
  3872  
  3873  	// create an object Part, will be used to test list object parts.
  3874  	_, err = obj.PutObjectPart(context.Background(), bucketName, testObject, uploadID, 1, mustGetPutObjReader(t, bytes.NewReader([]byte("hello")), int64(len("hello")), "5d41402abc4b2a76b9719d911017c592", ""), opts)
  3875  	if err != nil {
  3876  		t.Fatalf("MinIO %s : %s.", instanceType, err)
  3877  	}
  3878  
  3879  	// expected error types for invalid inputs to ListObjectParts handler.
  3880  	noAPIErr := APIError{}
  3881  	// expected error when the signature check fails.
  3882  	signatureMismatchErr := GetAPIError(ErrSignatureDoesNotMatch)
  3883  	// expected error the when the uploadID is invalid.
  3884  	noSuchUploadErr := GetAPIError(ErrNoSuchUpload)
  3885  	// expected error the part number marker use in the ListObjectParts request is invalid.
  3886  	invalidPartMarkerErr := GetAPIError(ErrInvalidPartNumberMarker)
  3887  	// expected error when the maximum number of parts requested to listed in invalid.
  3888  	invalidMaxPartsErr := GetAPIError(ErrInvalidMaxParts)
  3889  
  3890  	testCases := []struct {
  3891  		fault            Fault
  3892  		partNumberMarker string
  3893  		maxParts         string
  3894  		expectedErr      APIError
  3895  	}{
  3896  		// Test case - 1.
  3897  		// case where a signature mismatch is introduced and the response is validated.
  3898  		{
  3899  			fault:            BadSignature,
  3900  			partNumberMarker: "",
  3901  			maxParts:         "",
  3902  			expectedErr:      signatureMismatchErr,
  3903  		},
  3904  
  3905  		// Test case - 2.
  3906  		// Marker is set to invalid value of -1, error response is asserted.
  3907  		{
  3908  			fault:            None,
  3909  			partNumberMarker: "-1",
  3910  			maxParts:         "",
  3911  			expectedErr:      invalidPartMarkerErr,
  3912  		},
  3913  		// Test case - 3.
  3914  		// Max Parts is set a negative value, error response is validated.
  3915  		{
  3916  			fault:            None,
  3917  			partNumberMarker: "",
  3918  			maxParts:         "-1",
  3919  			expectedErr:      invalidMaxPartsErr,
  3920  		},
  3921  		// Test case - 4.
  3922  		// Invalid UploadID is set and the error response is validated.
  3923  		{
  3924  			fault:            MissingUploadID,
  3925  			partNumberMarker: "",
  3926  			maxParts:         "",
  3927  			expectedErr:      noSuchUploadErr,
  3928  		},
  3929  	}
  3930  
  3931  	// string to represent V2 signed HTTP request.
  3932  	reqV2Str := "V2 Signed HTTP request"
  3933  	// string to represent V4 signed HTTP request.
  3934  	reqV4Str := "V4 Signed HTTP request"
  3935  	// Collection of HTTP request and ResponseRecorder and request type string.
  3936  	type inputReqRec struct {
  3937  		req     *http.Request
  3938  		rec     *httptest.ResponseRecorder
  3939  		reqType string
  3940  	}
  3941  
  3942  	for i, test := range testCases {
  3943  		var reqV4, reqV2 *http.Request
  3944  		// Using sub-tests introduced in Go 1.7.
  3945  		t.Run(fmt.Sprintf("MinIO %s: Test case %d failed.", instanceType, i+1), func(t *testing.T) {
  3946  			recV2 := httptest.NewRecorder()
  3947  			recV4 := httptest.NewRecorder()
  3948  
  3949  			// setting a non-existent uploadID.
  3950  			// deliberately introducing the invalid value to be able to assert the response with the expected error response.
  3951  			if test.fault == MissingUploadID {
  3952  				uploadID = "upload1"
  3953  			}
  3954  
  3955  			// constructing a v4 signed HTTP request for ListMultipartUploads.
  3956  			reqV4, err = newTestSignedRequestV4(http.MethodGet,
  3957  				getListMultipartURLWithParams("", bucketName, testObject, uploadID, test.maxParts, test.partNumberMarker, ""),
  3958  				0, nil, credentials.AccessKey, credentials.SecretKey, nil)
  3959  
  3960  			if err != nil {
  3961  				t.Fatalf("Failed to create a V4 signed request to list object parts for %s/%s: <ERROR> %v.",
  3962  					bucketName, testObject, err)
  3963  			}
  3964  			// Verify response of the V2 signed HTTP request.
  3965  			// construct HTTP request for PutObject Part Object endpoint.
  3966  			reqV2, err = newTestSignedRequestV2(http.MethodGet,
  3967  				getListMultipartURLWithParams("", bucketName, testObject, uploadID, test.maxParts, test.partNumberMarker, ""),
  3968  				0, nil, credentials.AccessKey, credentials.SecretKey, nil)
  3969  
  3970  			if err != nil {
  3971  				t.Fatalf("Failed to create a V2 signed request to list object parts for %s/%s: <ERROR> %v.",
  3972  					bucketName, testObject, err)
  3973  			}
  3974  
  3975  			// collection of input HTTP request, ResponseRecorder and request type.
  3976  			reqRecs := []inputReqRec{
  3977  				{
  3978  					req:     reqV4,
  3979  					rec:     recV4,
  3980  					reqType: reqV4Str,
  3981  				},
  3982  				{
  3983  					req:     reqV2,
  3984  					rec:     recV2,
  3985  					reqType: reqV2Str,
  3986  				},
  3987  			}
  3988  			for _, reqRec := range reqRecs {
  3989  				// Response recorder to record the response of the handler.
  3990  				rec := reqRec.rec
  3991  				// HTTP request used to call the handler.
  3992  				req := reqRec.req
  3993  				// HTTP request type string for V4/V2 requests.
  3994  				reqType := reqRec.reqType
  3995  				// Malformed signature.
  3996  				if test.fault == BadSignature {
  3997  					req.Header.Set("authorization", req.Header.Get("authorization")+"a")
  3998  				}
  3999  
  4000  				// invoke the PutObjectPart HTTP handler with the given HTTP request.
  4001  				apiRouter.ServeHTTP(rec, req)
  4002  
  4003  				// validate the error response.
  4004  				if test.expectedErr != noAPIErr {
  4005  
  4006  					var errBytes []byte
  4007  					// read the response body.
  4008  					errBytes, err = ioutil.ReadAll(rec.Result().Body)
  4009  					if err != nil {
  4010  						t.Fatalf("%s,Failed to read error response list object parts request %s/%s: <ERROR> %v", reqType, bucketName, testObject, err)
  4011  					}
  4012  					// parse the error response.
  4013  					var errXML APIErrorResponse
  4014  					err = xml.Unmarshal(errBytes, &errXML)
  4015  					if err != nil {
  4016  						t.Fatalf("%s, Failed to unmarshal error response from list object partsest %s/%s: <ERROR> %v",
  4017  							reqType, bucketName, testObject, err)
  4018  					}
  4019  					// Validate whether the error has occurred for the expected reason.
  4020  					if test.expectedErr.Code != errXML.Code {
  4021  						t.Errorf("%s, Expected to fail with %s but received %s",
  4022  							reqType, test.expectedErr.Code, errXML.Code)
  4023  					}
  4024  					// in case error is not expected response status should be 200OK.
  4025  				} else {
  4026  					if rec.Code != http.StatusOK {
  4027  						t.Errorf("%s, Expected to succeed with response HTTP status 200OK, but failed with HTTP status code %d.", reqType, rec.Code)
  4028  					}
  4029  				}
  4030  			}
  4031  		})
  4032  	}
  4033  
  4034  	// Test for Anonymous/unsigned http request.
  4035  	anonReq, err := newTestRequest(http.MethodGet,
  4036  		getListMultipartURLWithParams("", bucketName, testObject, uploadIDCopy, "", "", ""), 0, nil)
  4037  	if err != nil {
  4038  		t.Fatalf("MinIO %s: Failed to create an anonymous request for %s/%s: <ERROR> %v",
  4039  			instanceType, bucketName, testObject, err)
  4040  	}
  4041  
  4042  	// ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse,
  4043  	// sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the
  4044  	// unsigned request goes through and its validated again.
  4045  	ExecObjectLayerAPIAnonTest(t, obj, "TestAPIListObjectPartsHandler", bucketName, testObject, instanceType, apiRouter, anonReq, getAnonWriteOnlyObjectPolicy(bucketName, testObject))
  4046  
  4047  	// HTTP request for testing when `objectLayer` is set to `nil`.
  4048  	// There is no need to use an existing bucket and valid input for creating the request
  4049  	// since the `objectLayer==nil`  check is performed before any other checks inside the handlers.
  4050  	// The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called.
  4051  	nilBucket := "dummy-bucket"
  4052  	nilObject := "dummy-object"
  4053  
  4054  	nilReq, err := newTestSignedRequestV4(http.MethodGet,
  4055  		getListMultipartURLWithParams("", nilBucket, nilObject, "dummy-uploadID", "0", "0", ""),
  4056  		0, nil, "", "", nil)
  4057  	if err != nil {
  4058  		t.Errorf("MinIO %s:Failed to create http request for testing the response when object Layer is set to `nil`.", instanceType)
  4059  	}
  4060  	// execute the object layer set to `nil` test.
  4061  	// `ExecObjectLayerAPINilTest` sets the Object Layer to `nil` and calls the handler.
  4062  	ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
  4063  }