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 }