gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }