api-list.go (31696B)
1 /* 2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage 3 * Copyright 2015-2020 MinIO, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package minio 19 20 import ( 21 "context" 22 "fmt" 23 "net/http" 24 "net/url" 25 "time" 26 27 "github.com/minio/minio-go/v7/pkg/s3utils" 28 ) 29 30 // ListBuckets list all buckets owned by this authenticated user. 31 // 32 // This call requires explicit authentication, no anonymous requests are 33 // allowed for listing buckets. 34 // 35 // api := client.New(....) 36 // for message := range api.ListBuckets(context.Background()) { 37 // fmt.Println(message) 38 // } 39 func (c *Client) ListBuckets(ctx context.Context) ([]BucketInfo, error) { 40 // Execute GET on service. 41 resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{contentSHA256Hex: emptySHA256Hex}) 42 defer closeResponse(resp) 43 if err != nil { 44 return nil, err 45 } 46 if resp != nil { 47 if resp.StatusCode != http.StatusOK { 48 return nil, httpRespToErrorResponse(resp, "", "") 49 } 50 } 51 listAllMyBucketsResult := listAllMyBucketsResult{} 52 err = xmlDecoder(resp.Body, &listAllMyBucketsResult) 53 if err != nil { 54 return nil, err 55 } 56 return listAllMyBucketsResult.Buckets.Bucket, nil 57 } 58 59 // Bucket List Operations. 60 func (c *Client) listObjectsV2(ctx context.Context, bucketName string, opts ListObjectsOptions) <-chan ObjectInfo { 61 // Allocate new list objects channel. 62 objectStatCh := make(chan ObjectInfo, 1) 63 // Default listing is delimited at "/" 64 delimiter := "/" 65 if opts.Recursive { 66 // If recursive we do not delimit. 67 delimiter = "" 68 } 69 70 // Return object owner information by default 71 fetchOwner := true 72 73 sendObjectInfo := func(info ObjectInfo) { 74 select { 75 case objectStatCh <- info: 76 case <-ctx.Done(): 77 } 78 } 79 80 // Validate bucket name. 81 if err := s3utils.CheckValidBucketName(bucketName); err != nil { 82 defer close(objectStatCh) 83 sendObjectInfo(ObjectInfo{ 84 Err: err, 85 }) 86 return objectStatCh 87 } 88 89 // Validate incoming object prefix. 90 if err := s3utils.CheckValidObjectNamePrefix(opts.Prefix); err != nil { 91 defer close(objectStatCh) 92 sendObjectInfo(ObjectInfo{ 93 Err: err, 94 }) 95 return objectStatCh 96 } 97 98 // Initiate list objects goroutine here. 99 go func(objectStatCh chan<- ObjectInfo) { 100 defer close(objectStatCh) 101 // Save continuationToken for next request. 102 var continuationToken string 103 for { 104 // Get list of objects a maximum of 1000 per request. 105 result, err := c.listObjectsV2Query(ctx, bucketName, opts.Prefix, continuationToken, 106 fetchOwner, opts.WithMetadata, delimiter, opts.StartAfter, opts.MaxKeys, opts.headers) 107 if err != nil { 108 sendObjectInfo(ObjectInfo{ 109 Err: err, 110 }) 111 return 112 } 113 114 // If contents are available loop through and send over channel. 115 for _, object := range result.Contents { 116 object.ETag = trimEtag(object.ETag) 117 select { 118 // Send object content. 119 case objectStatCh <- object: 120 // If receives done from the caller, return here. 121 case <-ctx.Done(): 122 return 123 } 124 } 125 126 // Send all common prefixes if any. 127 // NOTE: prefixes are only present if the request is delimited. 128 for _, obj := range result.CommonPrefixes { 129 select { 130 // Send object prefixes. 131 case objectStatCh <- ObjectInfo{Key: obj.Prefix}: 132 // If receives done from the caller, return here. 133 case <-ctx.Done(): 134 return 135 } 136 } 137 138 // If continuation token present, save it for next request. 139 if result.NextContinuationToken != "" { 140 continuationToken = result.NextContinuationToken 141 } 142 143 // Listing ends result is not truncated, return right here. 144 if !result.IsTruncated { 145 return 146 } 147 148 // Add this to catch broken S3 API implementations. 149 if continuationToken == "" { 150 sendObjectInfo(ObjectInfo{ 151 Err: fmt.Errorf("listObjectsV2 is truncated without continuationToken, %s S3 server is incompatible with S3 API", c.endpointURL), 152 }) 153 return 154 } 155 } 156 }(objectStatCh) 157 return objectStatCh 158 } 159 160 // listObjectsV2Query - (List Objects V2) - List some or all (up to 1000) of the objects in a bucket. 161 // 162 // You can use the request parameters as selection criteria to return a subset of the objects in a bucket. 163 // request parameters :- 164 // --------- 165 // ?prefix - Limits the response to keys that begin with the specified prefix. 166 // ?continuation-token - Used to continue iterating over a set of objects 167 // ?metadata - Specifies if we want metadata for the objects as part of list operation. 168 // ?delimiter - A delimiter is a character you use to group keys. 169 // ?start-after - Sets a marker to start listing lexically at this key onwards. 170 // ?max-keys - Sets the maximum number of keys returned in the response body. 171 func (c *Client) listObjectsV2Query(ctx context.Context, bucketName, objectPrefix, continuationToken string, fetchOwner, metadata bool, delimiter string, startAfter string, maxkeys int, headers http.Header) (ListBucketV2Result, error) { 172 // Validate bucket name. 173 if err := s3utils.CheckValidBucketName(bucketName); err != nil { 174 return ListBucketV2Result{}, err 175 } 176 // Validate object prefix. 177 if err := s3utils.CheckValidObjectNamePrefix(objectPrefix); err != nil { 178 return ListBucketV2Result{}, err 179 } 180 // Get resources properly escaped and lined up before 181 // using them in http request. 182 urlValues := make(url.Values) 183 184 // Always set list-type in ListObjects V2 185 urlValues.Set("list-type", "2") 186 187 if metadata { 188 urlValues.Set("metadata", "true") 189 } 190 191 // Set this conditionally if asked 192 if startAfter != "" { 193 urlValues.Set("start-after", startAfter) 194 } 195 196 // Always set encoding-type in ListObjects V2 197 urlValues.Set("encoding-type", "url") 198 199 // Set object prefix, prefix value to be set to empty is okay. 200 urlValues.Set("prefix", objectPrefix) 201 202 // Set delimiter, delimiter value to be set to empty is okay. 203 urlValues.Set("delimiter", delimiter) 204 205 // Set continuation token 206 if continuationToken != "" { 207 urlValues.Set("continuation-token", continuationToken) 208 } 209 210 // Fetch owner when listing 211 if fetchOwner { 212 urlValues.Set("fetch-owner", "true") 213 } 214 215 // Set max keys. 216 if maxkeys > 0 { 217 urlValues.Set("max-keys", fmt.Sprintf("%d", maxkeys)) 218 } 219 220 // Execute GET on bucket to list objects. 221 resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{ 222 bucketName: bucketName, 223 queryValues: urlValues, 224 contentSHA256Hex: emptySHA256Hex, 225 customHeader: headers, 226 }) 227 defer closeResponse(resp) 228 if err != nil { 229 return ListBucketV2Result{}, err 230 } 231 if resp != nil { 232 if resp.StatusCode != http.StatusOK { 233 return ListBucketV2Result{}, httpRespToErrorResponse(resp, bucketName, "") 234 } 235 } 236 237 // Decode listBuckets XML. 238 listBucketResult := ListBucketV2Result{} 239 if err = xmlDecoder(resp.Body, &listBucketResult); err != nil { 240 return listBucketResult, err 241 } 242 243 // This is an additional verification check to make 244 // sure proper responses are received. 245 if listBucketResult.IsTruncated && listBucketResult.NextContinuationToken == "" { 246 return listBucketResult, ErrorResponse{ 247 Code: "NotImplemented", 248 Message: "Truncated response should have continuation token set", 249 } 250 } 251 252 for i, obj := range listBucketResult.Contents { 253 listBucketResult.Contents[i].Key, err = decodeS3Name(obj.Key, listBucketResult.EncodingType) 254 if err != nil { 255 return listBucketResult, err 256 } 257 listBucketResult.Contents[i].LastModified = listBucketResult.Contents[i].LastModified.Truncate(time.Millisecond) 258 } 259 260 for i, obj := range listBucketResult.CommonPrefixes { 261 listBucketResult.CommonPrefixes[i].Prefix, err = decodeS3Name(obj.Prefix, listBucketResult.EncodingType) 262 if err != nil { 263 return listBucketResult, err 264 } 265 } 266 267 // Success. 268 return listBucketResult, nil 269 } 270 271 func (c *Client) listObjects(ctx context.Context, bucketName string, opts ListObjectsOptions) <-chan ObjectInfo { 272 // Allocate new list objects channel. 273 objectStatCh := make(chan ObjectInfo, 1) 274 // Default listing is delimited at "/" 275 delimiter := "/" 276 if opts.Recursive { 277 // If recursive we do not delimit. 278 delimiter = "" 279 } 280 281 sendObjectInfo := func(info ObjectInfo) { 282 select { 283 case objectStatCh <- info: 284 case <-ctx.Done(): 285 } 286 } 287 288 // Validate bucket name. 289 if err := s3utils.CheckValidBucketName(bucketName); err != nil { 290 defer close(objectStatCh) 291 sendObjectInfo(ObjectInfo{ 292 Err: err, 293 }) 294 return objectStatCh 295 } 296 // Validate incoming object prefix. 297 if err := s3utils.CheckValidObjectNamePrefix(opts.Prefix); err != nil { 298 defer close(objectStatCh) 299 sendObjectInfo(ObjectInfo{ 300 Err: err, 301 }) 302 return objectStatCh 303 } 304 305 // Initiate list objects goroutine here. 306 go func(objectStatCh chan<- ObjectInfo) { 307 defer close(objectStatCh) 308 309 marker := opts.StartAfter 310 for { 311 // Get list of objects a maximum of 1000 per request. 312 result, err := c.listObjectsQuery(ctx, bucketName, opts.Prefix, marker, delimiter, opts.MaxKeys, opts.headers) 313 if err != nil { 314 sendObjectInfo(ObjectInfo{ 315 Err: err, 316 }) 317 return 318 } 319 320 // If contents are available loop through and send over channel. 321 for _, object := range result.Contents { 322 // Save the marker. 323 marker = object.Key 324 select { 325 // Send object content. 326 case objectStatCh <- object: 327 // If receives done from the caller, return here. 328 case <-ctx.Done(): 329 return 330 } 331 } 332 333 // Send all common prefixes if any. 334 // NOTE: prefixes are only present if the request is delimited. 335 for _, obj := range result.CommonPrefixes { 336 select { 337 // Send object prefixes. 338 case objectStatCh <- ObjectInfo{Key: obj.Prefix}: 339 // If receives done from the caller, return here. 340 case <-ctx.Done(): 341 return 342 } 343 } 344 345 // If next marker present, save it for next request. 346 if result.NextMarker != "" { 347 marker = result.NextMarker 348 } 349 350 // Listing ends result is not truncated, return right here. 351 if !result.IsTruncated { 352 return 353 } 354 } 355 }(objectStatCh) 356 return objectStatCh 357 } 358 359 func (c *Client) listObjectVersions(ctx context.Context, bucketName string, opts ListObjectsOptions) <-chan ObjectInfo { 360 // Allocate new list objects channel. 361 resultCh := make(chan ObjectInfo, 1) 362 // Default listing is delimited at "/" 363 delimiter := "/" 364 if opts.Recursive { 365 // If recursive we do not delimit. 366 delimiter = "" 367 } 368 369 sendObjectInfo := func(info ObjectInfo) { 370 select { 371 case resultCh <- info: 372 case <-ctx.Done(): 373 } 374 } 375 376 // Validate bucket name. 377 if err := s3utils.CheckValidBucketName(bucketName); err != nil { 378 defer close(resultCh) 379 sendObjectInfo(ObjectInfo{ 380 Err: err, 381 }) 382 return resultCh 383 } 384 385 // Validate incoming object prefix. 386 if err := s3utils.CheckValidObjectNamePrefix(opts.Prefix); err != nil { 387 defer close(resultCh) 388 sendObjectInfo(ObjectInfo{ 389 Err: err, 390 }) 391 return resultCh 392 } 393 394 // Initiate list objects goroutine here. 395 go func(resultCh chan<- ObjectInfo) { 396 defer close(resultCh) 397 398 var ( 399 keyMarker = "" 400 versionIDMarker = "" 401 ) 402 403 for { 404 // Get list of objects a maximum of 1000 per request. 405 result, err := c.listObjectVersionsQuery(ctx, bucketName, opts, keyMarker, versionIDMarker, delimiter) 406 if err != nil { 407 sendObjectInfo(ObjectInfo{ 408 Err: err, 409 }) 410 return 411 } 412 413 // If contents are available loop through and send over channel. 414 for _, version := range result.Versions { 415 info := ObjectInfo{ 416 ETag: trimEtag(version.ETag), 417 Key: version.Key, 418 LastModified: version.LastModified.Truncate(time.Millisecond), 419 Size: version.Size, 420 Owner: version.Owner, 421 StorageClass: version.StorageClass, 422 IsLatest: version.IsLatest, 423 VersionID: version.VersionID, 424 IsDeleteMarker: version.isDeleteMarker, 425 UserTags: version.UserTags, 426 UserMetadata: version.UserMetadata, 427 } 428 select { 429 // Send object version info. 430 case resultCh <- info: 431 // If receives done from the caller, return here. 432 case <-ctx.Done(): 433 return 434 } 435 } 436 437 // Send all common prefixes if any. 438 // NOTE: prefixes are only present if the request is delimited. 439 for _, obj := range result.CommonPrefixes { 440 select { 441 // Send object prefixes. 442 case resultCh <- ObjectInfo{Key: obj.Prefix}: 443 // If receives done from the caller, return here. 444 case <-ctx.Done(): 445 return 446 } 447 } 448 449 // If next key marker is present, save it for next request. 450 if result.NextKeyMarker != "" { 451 keyMarker = result.NextKeyMarker 452 } 453 454 // If next version id marker is present, save it for next request. 455 if result.NextVersionIDMarker != "" { 456 versionIDMarker = result.NextVersionIDMarker 457 } 458 459 // Listing ends result is not truncated, return right here. 460 if !result.IsTruncated { 461 return 462 } 463 } 464 }(resultCh) 465 return resultCh 466 } 467 468 // listObjectVersions - (List Object Versions) - List some or all (up to 1000) of the existing objects 469 // and their versions in a bucket. 470 // 471 // You can use the request parameters as selection criteria to return a subset of the objects in a bucket. 472 // request parameters :- 473 // --------- 474 // ?key-marker - Specifies the key to start with when listing objects in a bucket. 475 // ?version-id-marker - Specifies the version id marker to start with when listing objects with versions in a bucket. 476 // ?delimiter - A delimiter is a character you use to group keys. 477 // ?prefix - Limits the response to keys that begin with the specified prefix. 478 // ?max-keys - Sets the maximum number of keys returned in the response body. 479 func (c *Client) listObjectVersionsQuery(ctx context.Context, bucketName string, opts ListObjectsOptions, keyMarker, versionIDMarker, delimiter string) (ListVersionsResult, error) { 480 // Validate bucket name. 481 if err := s3utils.CheckValidBucketName(bucketName); err != nil { 482 return ListVersionsResult{}, err 483 } 484 // Validate object prefix. 485 if err := s3utils.CheckValidObjectNamePrefix(opts.Prefix); err != nil { 486 return ListVersionsResult{}, err 487 } 488 // Get resources properly escaped and lined up before 489 // using them in http request. 490 urlValues := make(url.Values) 491 492 // Set versions to trigger versioning API 493 urlValues.Set("versions", "") 494 495 // Set object prefix, prefix value to be set to empty is okay. 496 urlValues.Set("prefix", opts.Prefix) 497 498 // Set delimiter, delimiter value to be set to empty is okay. 499 urlValues.Set("delimiter", delimiter) 500 501 // Set object marker. 502 if keyMarker != "" { 503 urlValues.Set("key-marker", keyMarker) 504 } 505 506 // Set max keys. 507 if opts.MaxKeys > 0 { 508 urlValues.Set("max-keys", fmt.Sprintf("%d", opts.MaxKeys)) 509 } 510 511 // Set version ID marker 512 if versionIDMarker != "" { 513 urlValues.Set("version-id-marker", versionIDMarker) 514 } 515 516 if opts.WithMetadata { 517 urlValues.Set("metadata", "true") 518 } 519 520 // Always set encoding-type 521 urlValues.Set("encoding-type", "url") 522 523 // Execute GET on bucket to list objects. 524 resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{ 525 bucketName: bucketName, 526 queryValues: urlValues, 527 contentSHA256Hex: emptySHA256Hex, 528 customHeader: opts.headers, 529 }) 530 defer closeResponse(resp) 531 if err != nil { 532 return ListVersionsResult{}, err 533 } 534 if resp != nil { 535 if resp.StatusCode != http.StatusOK { 536 return ListVersionsResult{}, httpRespToErrorResponse(resp, bucketName, "") 537 } 538 } 539 540 // Decode ListVersionsResult XML. 541 listObjectVersionsOutput := ListVersionsResult{} 542 err = xmlDecoder(resp.Body, &listObjectVersionsOutput) 543 if err != nil { 544 return ListVersionsResult{}, err 545 } 546 547 for i, obj := range listObjectVersionsOutput.Versions { 548 listObjectVersionsOutput.Versions[i].Key, err = decodeS3Name(obj.Key, listObjectVersionsOutput.EncodingType) 549 if err != nil { 550 return listObjectVersionsOutput, err 551 } 552 } 553 554 for i, obj := range listObjectVersionsOutput.CommonPrefixes { 555 listObjectVersionsOutput.CommonPrefixes[i].Prefix, err = decodeS3Name(obj.Prefix, listObjectVersionsOutput.EncodingType) 556 if err != nil { 557 return listObjectVersionsOutput, err 558 } 559 } 560 561 if listObjectVersionsOutput.NextKeyMarker != "" { 562 listObjectVersionsOutput.NextKeyMarker, err = decodeS3Name(listObjectVersionsOutput.NextKeyMarker, listObjectVersionsOutput.EncodingType) 563 if err != nil { 564 return listObjectVersionsOutput, err 565 } 566 } 567 568 return listObjectVersionsOutput, nil 569 } 570 571 // listObjects - (List Objects) - List some or all (up to 1000) of the objects in a bucket. 572 // 573 // You can use the request parameters as selection criteria to return a subset of the objects in a bucket. 574 // request parameters :- 575 // --------- 576 // ?marker - Specifies the key to start with when listing objects in a bucket. 577 // ?delimiter - A delimiter is a character you use to group keys. 578 // ?prefix - Limits the response to keys that begin with the specified prefix. 579 // ?max-keys - Sets the maximum number of keys returned in the response body. 580 func (c *Client) listObjectsQuery(ctx context.Context, bucketName, objectPrefix, objectMarker, delimiter string, maxkeys int, headers http.Header) (ListBucketResult, error) { 581 // Validate bucket name. 582 if err := s3utils.CheckValidBucketName(bucketName); err != nil { 583 return ListBucketResult{}, err 584 } 585 // Validate object prefix. 586 if err := s3utils.CheckValidObjectNamePrefix(objectPrefix); err != nil { 587 return ListBucketResult{}, err 588 } 589 // Get resources properly escaped and lined up before 590 // using them in http request. 591 urlValues := make(url.Values) 592 593 // Set object prefix, prefix value to be set to empty is okay. 594 urlValues.Set("prefix", objectPrefix) 595 596 // Set delimiter, delimiter value to be set to empty is okay. 597 urlValues.Set("delimiter", delimiter) 598 599 // Set object marker. 600 if objectMarker != "" { 601 urlValues.Set("marker", objectMarker) 602 } 603 604 // Set max keys. 605 if maxkeys > 0 { 606 urlValues.Set("max-keys", fmt.Sprintf("%d", maxkeys)) 607 } 608 609 // Always set encoding-type 610 urlValues.Set("encoding-type", "url") 611 612 // Execute GET on bucket to list objects. 613 resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{ 614 bucketName: bucketName, 615 queryValues: urlValues, 616 contentSHA256Hex: emptySHA256Hex, 617 customHeader: headers, 618 }) 619 defer closeResponse(resp) 620 if err != nil { 621 return ListBucketResult{}, err 622 } 623 if resp != nil { 624 if resp.StatusCode != http.StatusOK { 625 return ListBucketResult{}, httpRespToErrorResponse(resp, bucketName, "") 626 } 627 } 628 // Decode listBuckets XML. 629 listBucketResult := ListBucketResult{} 630 err = xmlDecoder(resp.Body, &listBucketResult) 631 if err != nil { 632 return listBucketResult, err 633 } 634 635 for i, obj := range listBucketResult.Contents { 636 listBucketResult.Contents[i].Key, err = decodeS3Name(obj.Key, listBucketResult.EncodingType) 637 if err != nil { 638 return listBucketResult, err 639 } 640 listBucketResult.Contents[i].LastModified = listBucketResult.Contents[i].LastModified.Truncate(time.Millisecond) 641 } 642 643 for i, obj := range listBucketResult.CommonPrefixes { 644 listBucketResult.CommonPrefixes[i].Prefix, err = decodeS3Name(obj.Prefix, listBucketResult.EncodingType) 645 if err != nil { 646 return listBucketResult, err 647 } 648 } 649 650 if listBucketResult.NextMarker != "" { 651 listBucketResult.NextMarker, err = decodeS3Name(listBucketResult.NextMarker, listBucketResult.EncodingType) 652 if err != nil { 653 return listBucketResult, err 654 } 655 } 656 657 return listBucketResult, nil 658 } 659 660 // ListObjectsOptions holds all options of a list object request 661 type ListObjectsOptions struct { 662 // Include objects versions in the listing 663 WithVersions bool 664 // Include objects metadata in the listing 665 WithMetadata bool 666 // Only list objects with the prefix 667 Prefix string 668 // Ignore '/' delimiter 669 Recursive bool 670 // The maximum number of objects requested per 671 // batch, advanced use-case not useful for most 672 // applications 673 MaxKeys int 674 // StartAfter start listing lexically at this 675 // object onwards, this value can also be set 676 // for Marker when `UseV1` is set to true. 677 StartAfter string 678 679 // Use the deprecated list objects V1 API 680 UseV1 bool 681 682 headers http.Header 683 } 684 685 // Set adds a key value pair to the options. The 686 // key-value pair will be part of the HTTP GET request 687 // headers. 688 func (o *ListObjectsOptions) Set(key, value string) { 689 if o.headers == nil { 690 o.headers = make(http.Header) 691 } 692 o.headers.Set(key, value) 693 } 694 695 // ListObjects returns objects list after evaluating the passed options. 696 // 697 // api := client.New(....) 698 // for object := range api.ListObjects(ctx, "mytestbucket", minio.ListObjectsOptions{Prefix: "starthere", Recursive:true}) { 699 // fmt.Println(object) 700 // } 701 func (c *Client) ListObjects(ctx context.Context, bucketName string, opts ListObjectsOptions) <-chan ObjectInfo { 702 if opts.WithVersions { 703 return c.listObjectVersions(ctx, bucketName, opts) 704 } 705 706 // Use legacy list objects v1 API 707 if opts.UseV1 { 708 return c.listObjects(ctx, bucketName, opts) 709 } 710 711 // Check whether this is snowball region, if yes ListObjectsV2 doesn't work, fallback to listObjectsV1. 712 if location, ok := c.bucketLocCache.Get(bucketName); ok { 713 if location == "snowball" { 714 return c.listObjects(ctx, bucketName, opts) 715 } 716 } 717 718 return c.listObjectsV2(ctx, bucketName, opts) 719 } 720 721 // ListIncompleteUploads - List incompletely uploaded multipart objects. 722 // 723 // ListIncompleteUploads lists all incompleted objects matching the 724 // objectPrefix from the specified bucket. If recursion is enabled 725 // it would list all subdirectories and all its contents. 726 // 727 // Your input parameters are just bucketName, objectPrefix, recursive. 728 // If you enable recursive as 'true' this function will return back all 729 // the multipart objects in a given bucket name. 730 // 731 // api := client.New(....) 732 // // Recurively list all objects in 'mytestbucket' 733 // recursive := true 734 // for message := range api.ListIncompleteUploads(context.Background(), "mytestbucket", "starthere", recursive) { 735 // fmt.Println(message) 736 // } 737 func (c *Client) ListIncompleteUploads(ctx context.Context, bucketName, objectPrefix string, recursive bool) <-chan ObjectMultipartInfo { 738 return c.listIncompleteUploads(ctx, bucketName, objectPrefix, recursive) 739 } 740 741 // listIncompleteUploads lists all incomplete uploads. 742 func (c *Client) listIncompleteUploads(ctx context.Context, bucketName, objectPrefix string, recursive bool) <-chan ObjectMultipartInfo { 743 // Allocate channel for multipart uploads. 744 objectMultipartStatCh := make(chan ObjectMultipartInfo, 1) 745 // Delimiter is set to "/" by default. 746 delimiter := "/" 747 if recursive { 748 // If recursive do not delimit. 749 delimiter = "" 750 } 751 // Validate bucket name. 752 if err := s3utils.CheckValidBucketName(bucketName); err != nil { 753 defer close(objectMultipartStatCh) 754 objectMultipartStatCh <- ObjectMultipartInfo{ 755 Err: err, 756 } 757 return objectMultipartStatCh 758 } 759 // Validate incoming object prefix. 760 if err := s3utils.CheckValidObjectNamePrefix(objectPrefix); err != nil { 761 defer close(objectMultipartStatCh) 762 objectMultipartStatCh <- ObjectMultipartInfo{ 763 Err: err, 764 } 765 return objectMultipartStatCh 766 } 767 go func(objectMultipartStatCh chan<- ObjectMultipartInfo) { 768 defer close(objectMultipartStatCh) 769 // object and upload ID marker for future requests. 770 var objectMarker string 771 var uploadIDMarker string 772 for { 773 // list all multipart uploads. 774 result, err := c.listMultipartUploadsQuery(ctx, bucketName, objectMarker, uploadIDMarker, objectPrefix, delimiter, 0) 775 if err != nil { 776 objectMultipartStatCh <- ObjectMultipartInfo{ 777 Err: err, 778 } 779 return 780 } 781 objectMarker = result.NextKeyMarker 782 uploadIDMarker = result.NextUploadIDMarker 783 784 // Send all multipart uploads. 785 for _, obj := range result.Uploads { 786 // Calculate total size of the uploaded parts if 'aggregateSize' is enabled. 787 select { 788 // Send individual uploads here. 789 case objectMultipartStatCh <- obj: 790 // If the context is canceled 791 case <-ctx.Done(): 792 return 793 } 794 } 795 // Send all common prefixes if any. 796 // NOTE: prefixes are only present if the request is delimited. 797 for _, obj := range result.CommonPrefixes { 798 select { 799 // Send delimited prefixes here. 800 case objectMultipartStatCh <- ObjectMultipartInfo{Key: obj.Prefix, Size: 0}: 801 // If context is canceled. 802 case <-ctx.Done(): 803 return 804 } 805 } 806 // Listing ends if result not truncated, return right here. 807 if !result.IsTruncated { 808 return 809 } 810 } 811 }(objectMultipartStatCh) 812 // return. 813 return objectMultipartStatCh 814 } 815 816 // listMultipartUploadsQuery - (List Multipart Uploads). 817 // - Lists some or all (up to 1000) in-progress multipart uploads in a bucket. 818 // 819 // You can use the request parameters as selection criteria to return a subset of the uploads in a bucket. 820 // request parameters. :- 821 // --------- 822 // ?key-marker - Specifies the multipart upload after which listing should begin. 823 // ?upload-id-marker - Together with key-marker specifies the multipart upload after which listing should begin. 824 // ?delimiter - A delimiter is a character you use to group keys. 825 // ?prefix - Limits the response to keys that begin with the specified prefix. 826 // ?max-uploads - Sets the maximum number of multipart uploads returned in the response body. 827 func (c *Client) listMultipartUploadsQuery(ctx context.Context, bucketName, keyMarker, uploadIDMarker, prefix, delimiter string, maxUploads int) (ListMultipartUploadsResult, error) { 828 // Get resources properly escaped and lined up before using them in http request. 829 urlValues := make(url.Values) 830 // Set uploads. 831 urlValues.Set("uploads", "") 832 // Set object key marker. 833 if keyMarker != "" { 834 urlValues.Set("key-marker", keyMarker) 835 } 836 // Set upload id marker. 837 if uploadIDMarker != "" { 838 urlValues.Set("upload-id-marker", uploadIDMarker) 839 } 840 841 // Set object prefix, prefix value to be set to empty is okay. 842 urlValues.Set("prefix", prefix) 843 844 // Set delimiter, delimiter value to be set to empty is okay. 845 urlValues.Set("delimiter", delimiter) 846 847 // Always set encoding-type 848 urlValues.Set("encoding-type", "url") 849 850 // maxUploads should be 1000 or less. 851 if maxUploads > 0 { 852 // Set max-uploads. 853 urlValues.Set("max-uploads", fmt.Sprintf("%d", maxUploads)) 854 } 855 856 // Execute GET on bucketName to list multipart uploads. 857 resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{ 858 bucketName: bucketName, 859 queryValues: urlValues, 860 contentSHA256Hex: emptySHA256Hex, 861 }) 862 defer closeResponse(resp) 863 if err != nil { 864 return ListMultipartUploadsResult{}, err 865 } 866 if resp != nil { 867 if resp.StatusCode != http.StatusOK { 868 return ListMultipartUploadsResult{}, httpRespToErrorResponse(resp, bucketName, "") 869 } 870 } 871 // Decode response body. 872 listMultipartUploadsResult := ListMultipartUploadsResult{} 873 err = xmlDecoder(resp.Body, &listMultipartUploadsResult) 874 if err != nil { 875 return listMultipartUploadsResult, err 876 } 877 878 listMultipartUploadsResult.NextKeyMarker, err = decodeS3Name(listMultipartUploadsResult.NextKeyMarker, listMultipartUploadsResult.EncodingType) 879 if err != nil { 880 return listMultipartUploadsResult, err 881 } 882 883 listMultipartUploadsResult.NextUploadIDMarker, err = decodeS3Name(listMultipartUploadsResult.NextUploadIDMarker, listMultipartUploadsResult.EncodingType) 884 if err != nil { 885 return listMultipartUploadsResult, err 886 } 887 888 for i, obj := range listMultipartUploadsResult.Uploads { 889 listMultipartUploadsResult.Uploads[i].Key, err = decodeS3Name(obj.Key, listMultipartUploadsResult.EncodingType) 890 if err != nil { 891 return listMultipartUploadsResult, err 892 } 893 } 894 895 for i, obj := range listMultipartUploadsResult.CommonPrefixes { 896 listMultipartUploadsResult.CommonPrefixes[i].Prefix, err = decodeS3Name(obj.Prefix, listMultipartUploadsResult.EncodingType) 897 if err != nil { 898 return listMultipartUploadsResult, err 899 } 900 } 901 902 return listMultipartUploadsResult, nil 903 } 904 905 // listObjectParts list all object parts recursively. 906 // 907 //lint:ignore U1000 Keep this around 908 func (c *Client) listObjectParts(ctx context.Context, bucketName, objectName, uploadID string) (partsInfo map[int]ObjectPart, err error) { 909 // Part number marker for the next batch of request. 910 var nextPartNumberMarker int 911 partsInfo = make(map[int]ObjectPart) 912 for { 913 // Get list of uploaded parts a maximum of 1000 per request. 914 listObjPartsResult, err := c.listObjectPartsQuery(ctx, bucketName, objectName, uploadID, nextPartNumberMarker, 1000) 915 if err != nil { 916 return nil, err 917 } 918 // Append to parts info. 919 for _, part := range listObjPartsResult.ObjectParts { 920 // Trim off the odd double quotes from ETag in the beginning and end. 921 part.ETag = trimEtag(part.ETag) 922 partsInfo[part.PartNumber] = part 923 } 924 // Keep part number marker, for the next iteration. 925 nextPartNumberMarker = listObjPartsResult.NextPartNumberMarker 926 // Listing ends result is not truncated, return right here. 927 if !listObjPartsResult.IsTruncated { 928 break 929 } 930 } 931 932 // Return all the parts. 933 return partsInfo, nil 934 } 935 936 // findUploadIDs lists all incomplete uploads and find the uploadIDs of the matching object name. 937 func (c *Client) findUploadIDs(ctx context.Context, bucketName, objectName string) ([]string, error) { 938 var uploadIDs []string 939 // Make list incomplete uploads recursive. 940 isRecursive := true 941 // List all incomplete uploads. 942 for mpUpload := range c.listIncompleteUploads(ctx, bucketName, objectName, isRecursive) { 943 if mpUpload.Err != nil { 944 return nil, mpUpload.Err 945 } 946 if objectName == mpUpload.Key { 947 uploadIDs = append(uploadIDs, mpUpload.UploadID) 948 } 949 } 950 // Return the latest upload id. 951 return uploadIDs, nil 952 } 953 954 // listObjectPartsQuery (List Parts query) 955 // - lists some or all (up to 1000) parts that have been uploaded 956 // for a specific multipart upload 957 // 958 // You can use the request parameters as selection criteria to return 959 // a subset of the uploads in a bucket, request parameters :- 960 // --------- 961 // ?part-number-marker - Specifies the part after which listing should 962 // begin. 963 // ?max-parts - Maximum parts to be listed per request. 964 func (c *Client) listObjectPartsQuery(ctx context.Context, bucketName, objectName, uploadID string, partNumberMarker, maxParts int) (ListObjectPartsResult, error) { 965 // Get resources properly escaped and lined up before using them in http request. 966 urlValues := make(url.Values) 967 // Set part number marker. 968 urlValues.Set("part-number-marker", fmt.Sprintf("%d", partNumberMarker)) 969 // Set upload id. 970 urlValues.Set("uploadId", uploadID) 971 972 // maxParts should be 1000 or less. 973 if maxParts > 0 { 974 // Set max parts. 975 urlValues.Set("max-parts", fmt.Sprintf("%d", maxParts)) 976 } 977 978 // Execute GET on objectName to get list of parts. 979 resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{ 980 bucketName: bucketName, 981 objectName: objectName, 982 queryValues: urlValues, 983 contentSHA256Hex: emptySHA256Hex, 984 }) 985 defer closeResponse(resp) 986 if err != nil { 987 return ListObjectPartsResult{}, err 988 } 989 if resp != nil { 990 if resp.StatusCode != http.StatusOK { 991 return ListObjectPartsResult{}, httpRespToErrorResponse(resp, bucketName, objectName) 992 } 993 } 994 // Decode list object parts XML. 995 listObjectPartsResult := ListObjectPartsResult{} 996 err = xmlDecoder(resp.Body, &listObjectPartsResult) 997 if err != nil { 998 return listObjectPartsResult, err 999 } 1000 return listObjectPartsResult, nil 1001 } 1002 1003 // Decode an S3 object name according to the encoding type 1004 func decodeS3Name(name, encodingType string) (string, error) { 1005 switch encodingType { 1006 case "url": 1007 return url.QueryUnescape(name) 1008 default: 1009 return name, nil 1010 } 1011 }