Repository: minio/minio-go
Pull request #1332 (2020-07-09 00:34:01)

re-implement CopyObject/ComposeObject API
new approach is simpler API style and predicable
allows future extensions as well

```
CopyObject(ctx, destOptions, srcOptions)
ComposeObject(ctx, destOptions, srcOptions....)
```


Patch:
diff --git a/.github/workflows/go-windows.yml b/.github/workflows/go-windows.yml
index b0824f3..91d2406 100644
--- a/.github/workflows/go-windows.yml
+++ b/.github/workflows/go-windows.yml
@@ -40,6 +40,6 @@ jobs:
           New-Item -ItemType Directory -Path "$env:temp/certs-dir"
           Copy-Item -Path testcerts\* -Destination "$env:temp/certs-dir"
           Invoke-WebRequest -Uri https://dl.minio.io/server/minio/release/windows-amd64/minio.exe -OutFile $HOME/minio.exe
-          Start-Process -NoNewWindow -FilePath "$HOME/minio.exe" -ArgumentList "-S", "$env:temp/certs-dir", "server", "$env:temp/fs"
+          Start-Process -NoNewWindow -FilePath "$HOME/minio.exe" -ArgumentList "-S", "$env:temp/certs-dir", "server", "$env:temp/fs{1...4}"
           $env:SSL_CERT_FILE = "$env:temp/certs-dir/public.crt"
           go run functional_tests.go
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index efd5ec9..ccc2a23 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -43,5 +43,5 @@ jobs:
           chmod +x /tmp/minio
           mkdir -p /tmp/certs-dir
           cp testcerts/* /tmp/certs-dir
-          /tmp/minio server --quiet -S /tmp/certs-dir /tmp/fs &
+          /tmp/minio server --quiet -S /tmp/certs-dir /tmp/fs{1...4} &
           make
diff --git a/api-compose-object.go b/api-compose-object.go
index 66582eb..15eaf3d 100644
--- a/api-compose-object.go
+++ b/api-compose-object.go
@@ -32,15 +32,11 @@ import (
 	"github.com/minio/minio-go/v7/pkg/s3utils"
 )
 
-// DestinationInfo - type with information about the object to be
-// created via server-side copy requests, using the Compose API.
-type DestinationInfo struct {
-	bucket, object string
-	opts           DestInfoOptions
-}
+// CopyDestOptions represents options specified by user for CopyObject/ComposeObject APIs
+type CopyDestOptions struct {
+	Bucket string // points to destination bucket
+	Object string // points to destination object
 
-// DestInfoOptions represents options specified by user for NewDestinationInfo call
-type DestInfoOptions struct {
 	// `Encryption` is the key info for server-side-encryption with customer
 	// provided key. If it is nil, no encryption is performed.
 	Encryption encrypt.ServerSide
@@ -53,7 +49,13 @@ type DestInfoOptions struct {
 	// if no user-metadata is provided, it is copied from source
 	// (when there is only once source object in the compose
 	// request)
-	UserMeta map[string]string
+	UserMetadata map[string]string
+	// UserMetadata is only set to destination if ReplaceMetadata is true
+	// other value is UserMetadata is ignored and we preserve src.UserMetadata
+	// NOTE: if you set this value to true and now metadata is present
+	// in UserMetadata your destination object will not have any metadata
+	// set.
+	ReplaceMetadata bool
 
 	// `userTags` is the user defined object tags to be set on destination.
 	// This will be set only if the `replaceTags` field is set to true.
@@ -67,12 +69,16 @@ type DestInfoOptions struct {
 	// Object Retention related fields
 	Mode            RetentionMode
 	RetainUntilDate time.Time
+
+	Size int64 // Needs to be specified if progress bar is specified.
+	// Progress of the entire copy operation will be sent here.
+	Progress io.Reader
 }
 
 // Process custom-metadata to remove a `x-amz-meta-` prefix if
 // present and validate that keys are distinct (after this
 // prefix removal).
-func filterCustomMeta(userMeta map[string]string) (map[string]string, error) {
+func filterCustomMeta(userMeta map[string]string) map[string]string {
 	m := make(map[string]string)
 	for k, v := range userMeta {
 		if strings.HasPrefix(strings.ToLower(k), "x-amz-meta-") {
@@ -83,172 +89,114 @@ func filterCustomMeta(userMeta map[string]string) (map[string]string, error) {
 		}
 		m[k] = v
 	}
-	return m, nil
+	return m
 }
 
-// NewDestinationInfo - creates a compose-object/copy-source
-// destination info object.
-func NewDestinationInfo(bucket, object string, destOpts DestInfoOptions) (d DestinationInfo, err error) {
-	// Input validation.
-	if err = s3utils.CheckValidBucketName(bucket); err != nil {
-		return d, err
-	}
-	if err = s3utils.CheckValidObjectName(object); err != nil {
-		return d, err
-	}
-	destOpts.UserMeta, err = filterCustomMeta(destOpts.UserMeta)
-	if err != nil {
-		return d, err
+// Marshal converts all the CopyDestOptions into their
+// equivalent HTTP header representation
+func (opts CopyDestOptions) Marshal(header http.Header) {
+	const replaceDirective = "REPLACE"
+	if opts.ReplaceTags {
+		header.Set(amzTaggingHeaderDirective, replaceDirective)
+		if tags := s3utils.TagEncode(opts.UserTags); tags != "" {
+			header.Set(amzTaggingHeader, tags)
+		}
 	}
-	return DestinationInfo{
-		bucket: bucket,
-		object: object,
-		opts:   destOpts,
-	}, nil
-}
 
-// getUserMetaHeadersMap - construct appropriate key-value pairs to send
-// as headers from metadata map to pass into copy-object request. For
-// single part copy-object (i.e. non-multipart object), enable the
-// withCopyDirectiveHeader to set the `x-amz-metadata-directive` to
-// `REPLACE`, so that metadata headers from the source are not copied
-// over.
-func (d *DestinationInfo) getUserMetaHeadersMap(withCopyDirectiveHeader bool) map[string]string {
-	if len(d.opts.UserMeta) == 0 {
-		return nil
-	}
-	r := make(map[string]string)
-	if withCopyDirectiveHeader {
-		r["x-amz-metadata-directive"] = "REPLACE"
-	}
-	for k, v := range d.opts.UserMeta {
-		if isAmzHeader(k) || isStandardHeader(k) || isStorageClassHeader(k) {
-			r[k] = v
-		} else {
-			r["x-amz-meta-"+k] = v
-		}
+	if opts.LegalHold != LegalHoldStatus("") {
+		header.Set(amzLegalHoldHeader, opts.LegalHold.String())
 	}
-	return r
-}
 
-// SourceInfo - represents a source object to be copied, using
-// server-side copying APIs.
-type SourceInfo struct {
-	bucket, object string
-	versionID      string
-	start, end     int64
-	encryption     encrypt.ServerSide
-	// Headers to send with the upload-part-copy request involving
-	// this source object.
-	Headers http.Header
-}
+	if opts.Mode != RetentionMode("") && !opts.RetainUntilDate.IsZero() {
+		header.Set(amzLockMode, opts.Mode.String())
+		header.Set(amzLockRetainUntil, opts.RetainUntilDate.Format(time.RFC3339))
+	}
 
-// NewSourceInfo - create a compose-object/copy-object source info
-// object.
-//
-// `decryptSSEC` is the decryption key using server-side-encryption
-// with customer provided key. It may be nil if the source is not
-// encrypted.
-func NewSourceInfo(bucket, object string, sse encrypt.ServerSide) SourceInfo {
-	r := SourceInfo{
-		bucket:     bucket,
-		object:     object,
-		start:      -1, // range is unspecified by default
-		encryption: sse,
-		Headers:    make(http.Header),
+	if opts.Encryption != nil {
+		opts.Encryption.Marshal(header)
 	}
 
-	// Set the source header
-	r.Headers.Set("x-amz-copy-source", s3utils.EncodePath(bucket+"/"+object))
-	return r
+	if opts.ReplaceMetadata {
+		header.Set("x-amz-metadata-directive", replaceDirective)
+		for k, v := range filterCustomMeta(opts.UserMetadata) {
+			if isAmzHeader(k) || isStandardHeader(k) || isStorageClassHeader(k) {
+				header.Set(k, v)
+			} else {
+				header.Set("x-amz-meta-"+k, v)
+			}
+		}
+	}
 }
 
-// SetVersionID - Set the version ID of the source object
-func (s *SourceInfo) SetVersionID(versionID string) error {
-	if versionID == "" {
-		return errInvalidArgument("version ID must be non-empty.")
+// toDestinationInfo returns a validated copyOptions object.
+func (opts CopyDestOptions) validate() (err error) {
+	// Input validation.
+	if err = s3utils.CheckValidBucketName(opts.Bucket); err != nil {
+		return err
+	}
+	if err = s3utils.CheckValidObjectName(opts.Object); err != nil {
+		return err
+	}
+	if opts.Progress != nil && opts.Size < 0 {
+		return errInvalidArgument("For progress bar effective size needs to be specified")
 	}
-	s.versionID = versionID
-	s.Headers.Set("x-amz-copy-source", s3utils.EncodePath(s.bucket+"/"+s.object)+"?versionId="+versionID)
 	return nil
 }
 
-// SetRange - Set the start and end offset of the source object to be
-// copied. If this method is not called, the whole source object is
-// copied.
-func (s *SourceInfo) SetRange(start, end int64) error {
-	if start > end || start < 0 {
-		return errInvalidArgument("start must be non-negative, and start must be at most end.")
-	}
-	// Note that 0 <= start <= end
-	s.start, s.end = start, end
-	return nil
+// CopySrcOptions represents a source object to be copied, using
+// server-side copying APIs.
+type CopySrcOptions struct {
+	Bucket, Object       string
+	VersionID            string
+	MatchETag            string
+	NoMatchETag          string
+	MatchModifiedSince   time.Time
+	MatchUnmodifiedSince time.Time
+	MatchRange           bool
+	Start, End           int64
+	Encryption           encrypt.ServerSide
 }
 
-// SetMatchETagCond - Set ETag match condition. The object is copied
-// only if the etag of the source matches the value given here.
-func (s *SourceInfo) SetMatchETagCond(etag string) error {
-	if etag == "" {
-		return errInvalidArgument("ETag cannot be empty.")
+// Marshal converts all the CopySrcOptions into their
+// equivalent HTTP header representation
+func (opts CopySrcOptions) Marshal(header http.Header) {
+	// Set the source header
+	header.Set("x-amz-copy-source", s3utils.EncodePath(opts.Bucket+"/"+opts.Object))
+	if opts.VersionID != "" {
+		header.Set("x-amz-copy-source", s3utils.EncodePath(opts.Bucket+"/"+opts.Object)+"?versionId="+opts.VersionID)
 	}
-	s.Headers.Set("x-amz-copy-source-if-match", etag)
-	return nil
-}
 
-// SetMatchETagExceptCond - Set the ETag match exception
-// condition. The object is copied only if the etag of the source is
-// not the value given here.
-func (s *SourceInfo) SetMatchETagExceptCond(etag string) error {
-	if etag == "" {
-		return errInvalidArgument("ETag cannot be empty.")
+	if opts.MatchETag != "" {
+		header.Set("x-amz-copy-source-if-match", opts.MatchETag)
+	}
+	if opts.NoMatchETag != "" {
+		header.Set("x-amz-copy-source-if-none-match", opts.NoMatchETag)
 	}
-	s.Headers.Set("x-amz-copy-source-if-none-match", etag)
-	return nil
-}
 
-// SetModifiedSinceCond - Set the modified since condition.
-func (s *SourceInfo) SetModifiedSinceCond(modTime time.Time) error {
-	if modTime.IsZero() {
-		return errInvalidArgument("Input time cannot be 0.")
+	if !opts.MatchModifiedSince.IsZero() {
+		header.Set("x-amz-copy-source-if-modified-since", opts.MatchModifiedSince.Format(http.TimeFormat))
+	}
+	if !opts.MatchUnmodifiedSince.IsZero() {
+		header.Set("x-amz-copy-source-if-unmodified-since", opts.MatchUnmodifiedSince.Format(http.TimeFormat))
 	}
-	s.Headers.Set("x-amz-copy-source-if-modified-since", modTime.Format(http.TimeFormat))
-	return nil
-}
 
-// SetUnmodifiedSinceCond - Set the unmodified since condition.
-func (s *SourceInfo) SetUnmodifiedSinceCond(modTime time.Time) error {
-	if modTime.IsZero() {
-		return errInvalidArgument("Input time cannot be 0.")
+	if opts.Encryption != nil {
+		encrypt.SSECopy(opts.Encryption).Marshal(header)
 	}
-	s.Headers.Set("x-amz-copy-source-if-unmodified-since", modTime.Format(http.TimeFormat))
-	return nil
 }
 
-// Helper to fetch size and etag of an object using a StatObject call.
-func (s *SourceInfo) getProps(c Client) (size int64, etag string, userMeta map[string]string, err error) {
-	// Get object info - need size and etag here. Also, decryption
-	// headers are added to the stat request if given.
-	var objInfo ObjectInfo
-	opts := StatObjectOptions{GetObjectOptions{ServerSideEncryption: encrypt.SSE(s.encryption)}}
-	if s.versionID != "" {
-		opts.VersionID = s.versionID
+func (opts CopySrcOptions) validate() (err error) {
+	// Input validation.
+	if err = s3utils.CheckValidBucketName(opts.Bucket); err != nil {
+		return err
 	}
-	objInfo, err = c.statObject(context.Background(), s.bucket, s.object, opts)
-	if err != nil {
-		err = errInvalidArgument(fmt.Sprintf("Could not stat object - %s/%s: %v", s.bucket, s.object, err))
-	} else {
-		size = objInfo.Size
-		etag = objInfo.ETag
-		userMeta = make(map[string]string)
-		for k, v := range objInfo.Metadata {
-			if strings.HasPrefix(k, "x-amz-meta-") {
-				if len(v) > 0 {
-					userMeta[k] = v[0]
-				}
-			}
-		}
+	if err = s3utils.CheckValidObjectName(opts.Object); err != nil {
+		return err
 	}
-	return
+	if opts.Start > opts.End || opts.Start < 0 {
+		return errInvalidArgument("start must be non-negative, and start must be at most end.")
+	}
+	return nil
 }
 
 // Low level implementation of CopyObject API, supports only upto 5GiB worth of copy.
@@ -384,64 +332,69 @@ func (c Client) uploadPartCopy(ctx context.Context, bucket, object, uploadID str
 	return p, nil
 }
 
-// ComposeObjectWithProgress - creates an object using server-side copying
+// ComposeObject - creates an object using server-side copying
 // of existing objects. It takes a list of source objects (with optional offsets)
 // and concatenates them into a new object using only server-side copying
 // operations. Optionally takes progress reader hook for applications to
 // look at current progress.
-func (c Client) ComposeObjectWithProgress(ctx context.Context, dst DestinationInfo, srcs []SourceInfo, progress io.Reader) (UploadInfo, error) {
+func (c Client) ComposeObject(ctx context.Context, dst CopyDestOptions, srcs ...CopySrcOptions) (UploadInfo, error) {
 	if len(srcs) < 1 || len(srcs) > maxPartsCount {
 		return UploadInfo{}, errInvalidArgument("There must be as least one and up to 10000 source objects.")
 	}
-	srcSizes := make([]int64, len(srcs))
-	var totalSize, size, totalParts int64
-	var srcUserMeta map[string]string
-	etags := make([]string, len(srcs))
+
+	for _, src := range srcs {
+		if err := src.validate(); err != nil {
+			return UploadInfo{}, err
+		}
+	}
+
+	if err := dst.validate(); err != nil {
+		return UploadInfo{}, err
+	}
+
+	srcObjectInfos := make([]ObjectInfo, len(srcs))
+	srcObjectSizes := make([]int64, len(srcs))
+	var totalSize, totalParts int64
 	var err error
 	for i, src := range srcs {
-		size, etags[i], srcUserMeta, err = src.getProps(c)
+		opts := StatObjectOptions{GetObjectOptions{ServerSideEncryption: encrypt.SSE(src.Encryption), VersionID: src.VersionID}}
+		srcObjectInfos[i], err = c.statObject(context.Background(), src.Bucket, src.Object, opts)
 		if err != nil {
 			return UploadInfo{}, err
 		}
 
-		// Error out if client side encryption is used in this source object when
-		// more than one source objects are given.
-		if len(srcs) > 1 && src.Headers.Get("x-amz-meta-x-amz-key") != "" {
-			return UploadInfo{}, errInvalidArgument(
-				fmt.Sprintf("Client side encryption is used in source object %s/%s", src.bucket, src.object))
-		}
-
+		srcCopySize := srcObjectInfos[i].Size
 		// Check if a segment is specified, and if so, is the
 		// segment within object bounds?
-		if src.start != -1 {
+		if src.MatchRange {
 			// Since range is specified,
 			//    0 <= src.start <= src.end
 			// so only invalid case to check is:
-			if src.end >= size {
+			if src.End >= srcCopySize || src.Start < 0 {
 				return UploadInfo{}, errInvalidArgument(
-					fmt.Sprintf("SourceInfo %d has invalid segment-to-copy [%d, %d] (size is %d)",
-						i, src.start, src.end, size))
+					fmt.Sprintf("CopySrcOptions %d has invalid segment-to-copy [%d, %d] (size is %d)",
+						i, src.Start, src.End, srcCopySize))
 			}
-			size = src.end - src.start + 1
+			srcCopySize = src.End - src.Start + 1
 		}
 
 		// Only the last source may be less than `absMinPartSize`
-		if size < absMinPartSize && i < len(srcs)-1 {
+		if srcCopySize < absMinPartSize && i < len(srcs)-1 {
 			return UploadInfo{}, errInvalidArgument(
-				fmt.Sprintf("SourceInfo %d is too small (%d) and it is not the last part", i, size))
+				fmt.Sprintf("CopySrcOptions %d is too small (%d) and it is not the last part", i, srcCopySize))
 		}
 
 		// Is data to copy too large?
-		totalSize += size
+		totalSize += srcCopySize
 		if totalSize > maxMultipartPutObjectSize {
 			return UploadInfo{}, errInvalidArgument(fmt.Sprintf("Cannot compose an object of size %d (> 5TiB)", totalSize))
 		}
 
 		// record source size
-		srcSizes[i] = size
+		srcObjectSizes[i] = srcCopySize
 
 		// calculate parts needed for current source
-		totalParts += partsRequired(size)
+		totalParts += partsRequired(srcCopySize)
 		// Do we need more parts than we are allowed?
 		if totalParts > maxPartsCount {
 			return UploadInfo{}, errInvalidArgument(fmt.Sprintf(
@@ -452,8 +405,8 @@ func (c Client) ComposeObjectWithProgress(ctx context.Context, dst DestinationIn
 	// Single source object case (i.e. when only one source is
 	// involved, it is being copied wholly and at most 5GiB in
 	// size, emptyfiles are also supported).
-	if (totalParts == 1 && srcs[0].start == -1 && totalSize <= maxPartSize) || (totalSize == 0) {
-		return c.CopyObjectWithProgress(ctx, dst, srcs[0], progress)
+	if (totalParts == 1 && srcs[0].Start == -1 && totalSize <= maxPartSize) || (totalSize == 0) {
+		return c.CopyObject(ctx, dst, srcs[0])
 	}
 
 	// Now, handle multipart-copy cases.
@@ -461,9 +414,7 @@ func (c Client) ComposeObjectWithProgress(ctx context.Context, dst DestinationIn
 	// 1. Ensure that the object has not been changed while
 	//    we are copying data.
 	for i, src := range srcs {
-		if src.Headers.Get("x-amz-copy-source-if-match") == "" {
-			src.SetMatchETagCond(etags[i])
-		}
+		src.MatchETag = srcObjectInfos[i].ETag
 	}
 
 	// 2. Initiate a new multipart upload.
@@ -471,17 +422,28 @@ func (c Client) ComposeObjectWithProgress(ctx context.Context, dst DestinationIn
 	// Set user-metadata on the destination object. If no
 	// user-metadata is specified, and there is only one source,
 	// (only) then metadata from source is copied.
-	userMeta := dst.getUserMetaHeadersMap(false)
-	metaMap := userMeta
-	if len(userMeta) == 0 && len(srcs) == 1 {
-		metaMap = srcUserMeta
+	var userMeta map[string]string
+	if dst.ReplaceMetadata {
+		userMeta = dst.UserMetadata
+	} else {
+		userMeta = srcObjectInfos[0].UserMetadata
 	}
-	metaHeaders := make(map[string]string)
-	for k, v := range metaMap {
-		metaHeaders[k] = v
+
+	var userTags map[string]string
+	if dst.ReplaceTags {
+		userTags = dst.UserTags
+	} else {
+		userTags = srcObjectInfos[0].UserTags
 	}
 
-	uploadID, err := c.newUploadID(ctx, dst.bucket, dst.object, PutObjectOptions{ServerSideEncryption: dst.opts.Encryption, UserMetadata: metaHeaders})
+	uploadID, err := c.newUploadID(ctx, dst.Bucket, dst.Object, PutObjectOptions{
+		ServerSideEncryption: dst.Encryption,
+		UserMetadata:         userMeta,
+		UserTags:             userTags,
+		Mode:                 dst.Mode,
+		RetainUntilDate:      dst.RetainUntilDate,
+		LegalHold:            dst.LegalHold,
+	})
 	if err != nil {
 		return UploadInfo{}, err
 	}
@@ -490,18 +452,12 @@ func (c Client) ComposeObjectWithProgress(ctx context.Context, dst DestinationIn
 	objParts := []CompletePart{}
 	partIndex := 1
 	for i, src := range srcs {
-		h := src.Headers
-		if src.encryption != nil {
-			encrypt.SSECopy(src.encryption).Marshal(h)
-		}
-		// Add destination encryption headers
-		if dst.opts.Encryption != nil {
-			dst.opts.Encryption.Marshal(h)
-		}
+		var h = make(http.Header)
+		src.Marshal(h)
 
 		// calculate start/end indices of parts after
 		// splitting.
-		startIdx, endIdx := calculateEvenSplits(srcSizes[i], src)
+		startIdx, endIdx := calculateEvenSplits(srcObjectSizes[i], src)
 		for j, start := range startIdx {
 			end := endIdx[j]
 
@@ -511,13 +467,13 @@ func (c Client) ComposeObjectWithProgress(ctx context.Context, dst DestinationIn
 				fmt.Sprintf("bytes=%d-%d", start, end))
 
 			// make upload-part-copy request
-			complPart, err := c.uploadPartCopy(ctx, dst.bucket,
-				dst.object, uploadID, partIndex, h)
+			complPart, err := c.uploadPartCopy(ctx, dst.Bucket,
+				dst.Object, uploadID, partIndex, h)
 			if err != nil {
 				return UploadInfo{}, err
 			}
-			if progress != nil {
-				io.CopyN(ioutil.Discard, progress, end-start+1)
+			if dst.Progress != nil {
+				io.CopyN(ioutil.Discard, dst.Progress, end-start+1)
 			}
 			objParts = append(objParts, complPart)
 			partIndex++
@@ -525,7 +481,7 @@ func (c Client) ComposeObjectWithProgress(ctx context.Context, dst DestinationIn
 	}
 
 	// 4. Make final complete-multipart request.
-	uploadInfo, err := c.completeMultipartUpload(ctx, dst.bucket, dst.object, uploadID,
+	uploadInfo, err := c.completeMultipartUpload(ctx, dst.Bucket, dst.Object, uploadID,
 		completeMultipartUpload{Parts: objParts})
 	if err != nil {
 		return UploadInfo{}, err
@@ -535,14 +491,6 @@ func (c Client) ComposeObjectWithProgress(ctx context.Context, dst DestinationIn
 	return uploadInfo, nil
 }
 
-// ComposeObject - creates an object using server-side copying of
-// existing objects. It takes a list of source objects (with optional
-// offsets) and concatenates them into a new object using only
-// server-side copying operations.
-func (c Client) ComposeObject(ctx context.Context, dst DestinationInfo, srcs []SourceInfo) (UploadInfo, error) {
-	return c.ComposeObjectWithProgress(ctx, dst, srcs, nil)
-}
-
 // partsRequired is maximum parts possible with
 // max part size of ceiling(maxMultipartPutObjectSize / (maxPartsCount - 1))
 func partsRequired(size int64) int64 {
@@ -558,7 +506,7 @@ func partsRequired(size int64) int64 {
 // start and end index slices. Splits happen evenly to be sure that no
 // part is less than 5MiB, as that could fail the multipart request if
 // it is not the last part.
-func calculateEvenSplits(size int64, src SourceInfo) (startIndex, endIndex []int64) {
+func calculateEvenSplits(size int64, src CopySrcOptions) (startIndex, endIndex []int64) {
 	if size == 0 {
 		return
 	}
@@ -579,7 +527,7 @@ func calculateEvenSplits(size int64, src SourceInfo) (startIndex, endIndex []int
 	// size = q * k + r (by simple division of size by k,
 	// so that 0 <= r < k)
 	//
-	start := src.start
+	start := src.Start
 	if start == -1 {
 		start = 0
 	}
diff --git a/api-datatypes.go b/api-datatypes.go
index d4dbfe7..890b602 100644
--- a/api-datatypes.go
+++ b/api-datatypes.go
@@ -71,9 +71,14 @@ type Owner struct {
 // UploadInfo contains information about the
 // newly uploaded or copied object.
 type UploadInfo struct {
-	ETag      string
-	VersionID string
-	Size      int64
+	Bucket       string
+	Key          string
+	ETag         string
+	Size         int64
+	LastModified time.Time
+	Expiration   time.Time
+	Location     string
+	VersionID    string
 }
 
 // ObjectInfo container for object metadata.
diff --git a/api-list.go b/api-list.go
index a683f81..7845424 100644
--- a/api-list.go
+++ b/api-list.go
@@ -58,7 +58,7 @@ func (c Client) ListBuckets(ctx context.Context) ([]BucketInfo, error) {
 
 /// Bucket Read Operations.
 
-func (c Client) listObjectsV2(ctx context.Context, bucketName, objectPrefix, marker string, recursive, metadata bool, maxKeys int) <-chan ObjectInfo {
+func (c Client) listObjectsV2(ctx context.Context, bucketName, objectPrefix string, recursive, metadata bool, maxKeys int) <-chan ObjectInfo {
 	// Allocate new list objects channel.
 	objectStatCh := make(chan ObjectInfo, 1)
 	// Default listing is delimited at "/"
@@ -97,7 +97,7 @@ func (c Client) listObjectsV2(ctx context.Context, bucketName, objectPrefix, mar
 		for {
 			// Get list of objects a maximum of 1000 per request.
 			result, err := c.listObjectsV2Query(ctx, bucketName, objectPrefix, continuationToken,
-				fetchOwner, metadata, delimiter, maxKeys, marker)
+				fetchOwner, metadata, delimiter, maxKeys)
 			if err != nil {
 				objectStatCh <- ObjectInfo{
 					Err: err,
@@ -152,9 +152,8 @@ func (c Client) listObjectsV2(ctx context.Context, bucketName, objectPrefix, mar
 // ?delimiter - A delimiter is a character you use to group keys.
 // ?prefix - Limits the response to keys that begin with the specified prefix.
 // ?max-keys - Sets the maximum number of keys returned in the response body.
-// ?start-after - Specifies the key to start after when listing objects in a bucket.
 // ?metadata - Specifies if we want metadata for the objects as part of list operation.
-func (c Client) listObjectsV2Query(ctx context.Context, bucketName, objectPrefix, continuationToken string, fetchOwner, metadata bool, delimiter string, maxkeys int, startAfter string) (ListBucketV2Result, error) {
+func (c Client) listObjectsV2Query(ctx context.Context, bucketName, objectPrefix, continuationToken string, fetchOwner, metadata bool, delimiter string, maxkeys int) (ListBucketV2Result, error) {
 	// Validate bucket name.
 	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
 		return ListBucketV2Result{}, err
@@ -198,11 +197,6 @@ func (c Client) listObjectsV2Query(ctx context.Context, bucketName, objectPrefix
 		urlValues.Set("max-keys", fmt.Sprintf("%d", maxkeys))
 	}
 
-	// Set start-after
-	if startAfter != "" {
-		urlValues.Set("start-after", startAfter)
-	}
-
 	// Execute GET on bucket to list objects.
 	resp, err := c.executeMethod(ctx, "GET", requestMetadata{
 		bucketName:       bucketName,
@@ -252,7 +246,7 @@ func (c Client) listObjectsV2Query(ctx context.Context, bucketName, objectPrefix
 	return listBucketResult, nil
 }
 
-func (c Client) listObjects(ctx context.Context, bucketName, objectPrefix, marker string, recursive bool, maxKeys int) <-chan ObjectInfo {
+func (c Client) listObjects(ctx context.Context, bucketName, objectPrefix string, recursive bool, maxKeys int) <-chan ObjectInfo {
 	// Allocate new list objects channel.
 	objectStatCh := make(chan ObjectInfo, 1)
 	// Default listing is delimited at "/"
@@ -282,6 +276,7 @@ func (c Client) listObjects(ctx context.Context, bucketName, objectPrefix, marke
 	go func(objectStatCh chan<- ObjectInfo) {
 		defer close(objectStatCh)
 
+		marker := ""
 		for {
 			// Get list of objects a maximum of 1000 per request.
 			result, err := c.listObjectsQuery(ctx, bucketName, objectPrefix, marker, delimiter, maxKeys)
@@ -331,7 +326,7 @@ func (c Client) listObjects(ctx context.Context, bucketName, objectPrefix, marke
 	return objectStatCh
 }
 
-func (c Client) listObjectVersions(ctx context.Context, bucketName, prefix, marker string, recursive bool, maxKeys int) <-chan ObjectInfo {
+func (c Client) listObjectVersions(ctx context.Context, bucketName, prefix string, recursive bool, maxKeys int) <-chan ObjectInfo {
 	// Allocate new list objects channel.
 	resultCh := make(chan ObjectInfo, 1)
 	// Default listing is delimited at "/"
@@ -364,7 +359,7 @@ func (c Client) listObjectVersions(ctx context.Context, bucketName, prefix, mark
 		defer close(resultCh)
 
 		var (
-			keyMarker       = marker
+			keyMarker       = ""
 			versionIDMarker = ""
 		)
 
@@ -619,8 +614,6 @@ func (c Client) listObjectsQuery(ctx context.Context, bucketName, objectPrefix,
 
 // ListObjectsOptions holds all options of a list object request
 type ListObjectsOptions struct {
-	// Force the deprecated listing V1
-	ForceV1 bool
 	// Include objects versions in the listing
 	WithVersions bool
 	// Include objects metadata in the listing
@@ -629,10 +622,13 @@ type ListObjectsOptions struct {
 	Prefix string
 	// Ignore '/' delimiter
 	Recursive bool
-	// Start listing after the specified path
-	StartAfter string
-	// The maximum number of returned objects per request
+	// The maximum number of objects requested per
+	// batch, advanced use-case not useful for most
+	// applications
 	MaxKeys int
+
+	// Use the deprecated list objects V1 API
+	UseV1 bool
 }
 
 // ListObjects returns objects list after evaluating the passed options.
@@ -644,21 +640,22 @@ type ListObjectsOptions struct {
 //
 func (c Client) ListObjects(ctx context.Context, bucketName string, opts ListObjectsOptions) <-chan ObjectInfo {
 	if opts.WithVersions {
-		return c.listObjectVersions(ctx, bucketName, opts.Prefix, opts.StartAfter, opts.Recursive, opts.MaxKeys)
+		return c.listObjectVersions(ctx, bucketName, opts.Prefix, opts.Recursive, opts.MaxKeys)
 	}
 
-	if opts.ForceV1 {
-		return c.listObjects(ctx, bucketName, opts.Prefix, opts.StartAfter, opts.Recursive, opts.MaxKeys)
+	// Use legacy list objects v1 API
+	if opts.UseV1 {
+		return c.listObjects(ctx, bucketName, opts.Prefix, opts.Recursive, opts.MaxKeys)
 	}
 
 	// Check whether this is snowball region, if yes ListObjectsV2 doesn't work, fallback to listObjectsV1.
 	if location, ok := c.bucketLocCache.Get(bucketName); ok {
 		if location == "snowball" {
-			return c.listObjects(ctx, bucketName, opts.Prefix, opts.StartAfter, opts.Recursive, opts.MaxKeys)
+			return c.listObjects(ctx, bucketName, opts.Prefix, opts.Recursive, opts.MaxKeys)
 		}
 	}
 
-	return c.listObjectsV2(ctx, bucketName, opts.Prefix, opts.StartAfter, opts.Recursive, opts.WithMetadata, opts.MaxKeys)
+	return c.listObjectsV2(ctx, bucketName, opts.Prefix, opts.Recursive, opts.WithMetadata, opts.MaxKeys)
 }
 
 // ListIncompleteUploads - List incompletely uploaded multipart objects.
diff --git a/api-put-object-copy.go b/api-put-object-copy.go
index baf7247..13e17dc 100644
--- a/api-put-object-copy.go
+++ b/api-put-object-copy.go
@@ -22,62 +22,25 @@ import (
 	"io"
 	"io/ioutil"
 	"net/http"
-	"time"
-
-	"github.com/minio/minio-go/v7/pkg/encrypt"
-	"github.com/minio/minio-go/v7/pkg/s3utils"
 )
 
 // CopyObject - copy a source object into a new object
-func (c Client) CopyObject(ctx context.Context, dst DestinationInfo, src SourceInfo) (UploadInfo, error) {
-	return c.CopyObjectWithProgress(ctx, dst, src, nil)
-}
-
-// CopyObjectWithProgress is like CopyObject with additional progress bar.
-func (c Client) CopyObjectWithProgress(ctx context.Context, dst DestinationInfo, src SourceInfo, progress io.Reader) (UploadInfo, error) {
-	header := make(http.Header)
-	for k, v := range src.Headers {
-		header[k] = v
-	}
-
-	if dst.opts.ReplaceTags && len(dst.opts.UserTags) != 0 {
-		header.Set(amzTaggingHeaderDirective, "REPLACE")
-		header.Set(amzTaggingHeader, s3utils.TagEncode(dst.opts.UserTags))
-	}
-
-	if dst.opts.LegalHold != LegalHoldStatus("") {
-		header.Set(amzLegalHoldHeader, dst.opts.LegalHold.String())
-	}
-
-	if dst.opts.Mode != RetentionMode("") && !dst.opts.RetainUntilDate.IsZero() {
-		header.Set(amzLockMode, dst.opts.Mode.String())
-		header.Set(amzLockRetainUntil, dst.opts.RetainUntilDate.Format(time.RFC3339))
-	}
-
-	var err error
-	var size int64
-	// If progress bar is specified, size should be requested as well initiate a StatObject request.
-	if progress != nil {
-		size, _, _, err = src.getProps(c)
-		if err != nil {
-			return UploadInfo{}, err
-		}
+func (c Client) CopyObject(ctx context.Context, dst CopyDestOptions, src CopySrcOptions) (UploadInfo, error) {
+	if err := src.validate(); err != nil {
+		return UploadInfo{}, err
 	}
 
-	if src.encryption != nil {
-		encrypt.SSECopy(src.encryption).Marshal(header)
+	if err := dst.validate(); err != nil {
+		return UploadInfo{}, err
 	}
 
-	if dst.opts.Encryption != nil {
-		dst.opts.Encryption.Marshal(header)
-	}
-	for k, v := range dst.getUserMetaHeadersMap(true) {
-		header.Set(k, v)
-	}
+	header := make(http.Header)
+	dst.Marshal(header)
+	src.Marshal(header)
 
-	resp, err := c.executeMethod(ctx, "PUT", requestMetadata{
-		bucketName:   dst.bucket,
-		objectName:   dst.object,
+	resp, err := c.executeMethod(ctx, http.MethodPut, requestMetadata{
+		bucketName:   dst.Bucket,
+		objectName:   dst.Object,
 		customHeader: header,
 	})
 	if err != nil {
@@ -86,17 +49,24 @@ func (c Client) CopyObjectWithProgress(ctx context.Context, dst DestinationInfo,
 	defer closeResponse(resp)
 
 	if resp.StatusCode != http.StatusOK {
-		return UploadInfo{}, httpRespToErrorResponse(resp, dst.bucket, dst.object)
+		return UploadInfo{}, httpRespToErrorResponse(resp, dst.Bucket, dst.Object)
 	}
 
 	// Update the progress properly after successful copy.
-	if progress != nil {
-		io.CopyN(ioutil.Discard, progress, size)
+	if dst.Progress != nil {
+		io.Copy(ioutil.Discard, io.LimitReader(dst.Progress, dst.Size))
+	}
+
+	cpObjRes := copyObjectResult{}
+	if err = xmlDecoder(resp.Body, &cpObjRes); err != nil {
+		return UploadInfo{}, err
 	}
 
 	return UploadInfo{
-		VersionID: resp.Header.Get("x-amz-version-id"),
-		Size:      size,
-		ETag:      trimEtag(resp.Header.Get("ETag")),
+		Bucket:       dst.Bucket,
+		Key:          dst.Object,
+		LastModified: cpObjRes.LastModified,
+		VersionID:    resp.Header.Get("x-amz-version-id"),
+		ETag:         trimEtag(resp.Header.Get("ETag")),
 	}, nil
 }
diff --git a/api-put-object-multipart.go b/api-put-object-multipart.go
index 978b265..5d8da15 100644
--- a/api-put-object-multipart.go
+++ b/api-put-object-multipart.go
@@ -369,8 +369,11 @@ func (c Client) completeMultipartUpload(ctx context.Context, bucketName, objectN
 	}
 
 	return UploadInfo{
+		Bucket:    completeMultipartUploadResult.Bucket,
+		Key:       completeMultipartUploadResult.Key,
 		ETag:      trimEtag(completeMultipartUploadResult.ETag),
 		VersionID: resp.Header.Get("x-amz-version-id"),
+		Location:  completeMultipartUploadResult.Location,
 	}, nil
 
 }
diff --git a/api-put-object-streaming.go b/api-put-object-streaming.go
index 4c8393b..74e4330 100644
--- a/api-put-object-streaming.go
+++ b/api-put-object-streaming.go
@@ -451,8 +451,10 @@ func (c Client) putObjectDo(ctx context.Context, bucketName, objectName string,
 	}
 
 	return UploadInfo{
+		Bucket:    bucketName,
+		Key:       objectName,
 		ETag:      trimEtag(resp.Header.Get("ETag")),
-		VersionID: resp.Header.Get("x-amz-version-id"),
 		Size:      size,
+		VersionID: resp.Header.Get("x-amz-version-id"),
 	}, nil
 }
diff --git a/api-put-object.go b/api-put-object.go
index 6b7b6e8..dbc830b 100644
--- a/api-put-object.go
+++ b/api-put-object.go
@@ -43,8 +43,8 @@ type PutObjectOptions struct {
 	ContentDisposition      string
 	ContentLanguage         string
 	CacheControl            string
-	Mode                    *RetentionMode
-	RetainUntilDate         *time.Time
+	Mode                    RetentionMode
+	RetainUntilDate         time.Time
 	ServerSideEncryption    encrypt.ServerSide
 	NumThreads              uint
 	StorageClass            string
@@ -92,11 +92,11 @@ func (opts PutObjectOptions) Header() (header http.Header) {
 		header.Set("Cache-Control", opts.CacheControl)
 	}
 
-	if opts.Mode != nil {
+	if opts.Mode != "" {
 		header.Set(amzLockMode, opts.Mode.String())
 	}
 
-	if opts.RetainUntilDate != nil {
+	if !opts.RetainUntilDate.IsZero() {
 		header.Set("X-Amz-Object-Lock-Retain-Until-Date", opts.RetainUntilDate.Format(time.RFC3339))
 	}
 
@@ -119,15 +119,16 @@ func (opts PutObjectOptions) Header() (header http.Header) {
 	if opts.ReplicationStatus != "" {
 		header.Set(amzBucketReplicationStatus, opts.ReplicationStatus)
 	}
+
 	if len(opts.UserTags) != 0 {
 		header.Set(amzTaggingHeader, s3utils.TagEncode(opts.UserTags))
 	}
 
 	for k, v := range opts.UserMetadata {
-		if !isAmzHeader(k) && !isStandardHeader(k) && !isStorageClassHeader(k) {
-			header.Set("X-Amz-Meta-"+k, v)
-		} else {
+		if isAmzHeader(k) || isStandardHeader(k) || isStorageClassHeader(k) {
 			header.Set(k, v)
+		} else {
+			header.Set("x-amz-meta-"+k, v)
 		}
 	}
 	return
@@ -143,10 +144,8 @@ func (opts PutObjectOptions) validate() (err error) {
 			return errInvalidArgument(v + " unsupported user defined metadata value")
 		}
 	}
-	if opts.Mode != nil {
-		if !opts.Mode.IsValid() {
-			return errInvalidArgument(opts.Mode.String() + " unsupported retention mode")
-		}
+	if opts.Mode != "" && !opts.Mode.IsValid() {
+		return errInvalidArgument(opts.Mode.String() + " unsupported retention mode")
 	}
 	if opts.LegalHold != "" && !opts.LegalHold.IsValid() {
 		return errInvalidArgument(opts.LegalHold.String() + " unsupported legal-hold status")
diff --git a/core.go b/core.go
index a7f6c72..f607322 100644
--- a/core.go
+++ b/core.go
@@ -51,8 +51,8 @@ func (c Core) ListObjects(bucket, prefix, marker, delimiter string, maxKeys int)
 
 // ListObjectsV2 - Lists all the objects at a prefix, similar to ListObjects() but uses
 // continuationToken instead of marker to support iteration over the results.
-func (c Core) ListObjectsV2(bucketName, objectPrefix, continuationToken string, fetchOwner bool, delimiter string, maxkeys int, startAfter string) (ListBucketV2Result, error) {
-	return c.listObjectsV2Query(context.Background(), bucketName, objectPrefix, continuationToken, fetchOwner, false, delimiter, maxkeys, startAfter)
+func (c Core) ListObjectsV2(bucketName, objectPrefix, continuationToken string, fetchOwner bool, delimiter string, maxkeys int) (ListBucketV2Result, error) {
+	return c.listObjectsV2Query(context.Background(), bucketName, objectPrefix, continuationToken, fetchOwner, false, delimiter, maxkeys)
 }
 
 // CopyObject - copies an object from source object to destination object on server side.
diff --git a/docs/API.md b/docs/API.md
index 61067f2..b94217f 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -50,29 +50,29 @@ func main() {
 }
 ```
 
-| Bucket operations                                       | Object operations                                                     | Encrypted Object operations                       | Presigned operations                          | Bucket Policy/Notification Operations                         | Client custom settings                                |
-| :---                                                    | :---                                                                  | :---                                              | :---                                          | :---                                                          | :---                                                  |
-| [`MakeBucket`](#MakeBucket)                             | [`GetObject`](#GetObject)                                             | [`GetObject`](#GetObject)                         | [`PresignedGetObject`](#PresignedGetObject)   | [`SetBucketPolicy`](#SetBucketPolicy)                         | [`SetAppInfo`](#SetAppInfo)                           |
-| [`MakeBucketWithObjectLock`](#MakeBucketWithObjectLock) | [`PutObject`](#PutObject)                                             | [`PutObject`](#PutObject)                         | [`PresignedPutObject`](#PresignedPutObject)   | [`GetBucketPolicy`](#GetBucketPolicy)                         | [`SetCustomTransport`](#SetCustomTransport)           |
-| [`ListBuckets`](#ListBuckets)                           | [`CopyObject`](#CopyObject)                                           | [`CopyObject`](#CopyObject)                       | [`PresignedPostPolicy`](#PresignedPostPolicy) | [`SetBucketNotification`](#SetBucketNotification)             | [`TraceOn`](#TraceOn)                                 |
-| [`BucketExists`](#BucketExists)                         | [`StatObject`](#StatObject)                                           | [`StatObject`](#StatObject)                       |                                               | [`GetBucketNotification`](#GetBucketNotification)             | [`TraceOff`](#TraceOff)                               |
-| [`RemoveBucket`](#RemoveBucket)                         | [`RemoveObject`](#RemoveObject)                                       | [`FPutObject`](#FPutObject)                       |                                               | [`RemoveAllBucketNotification`](#RemoveAllBucketNotification) | [`SetS3TransferAccelerate`](#SetS3TransferAccelerate) |
-| [`ListObjects`](#ListObjects)                           | [`RemoveObjects`](#RemoveObjects)                                     | [`FGetObject`](#FGetObject)                       |                                               | [`ListenBucketNotification`](#ListenBucketNotification)       |                                                       |
-| [`ListObjectsV2`](#ListObjectsV2)                       | [`RemoveIncompleteUpload`](#RemoveIncompleteUpload)                   | [`ComposeObject`](#ComposeObjecet)                |                                               | [`SetBucketLifecycle`](#SetBucketLifecycle)                   |                                                       |
-| [`ListIncompleteUploads`](#ListIncompleteUploads)       | [`FPutObject`](#FPutObject)                                           | [`NewSourceInfo`](#NewSourceInfo)                 |                                               | [`GetBucketLifecycle`](#GetBucketLifecycle)                   |                                                       |
-| [`SetBucketTagging`](#SetBucketTagging)                 | [`FGetObject`](#FGetObject)                                           | [`NewDestinationInfo`](#NewDestinationInfo)       |                                               | [`SetObjectLockConfig`](#SetObjectLockConfig)                 |                                                       |
-| [`GetBucketTagging`](#GetBucketTagging)                 | [`ComposeObject`](#ComposeObject)                                     |                                                   |                                               | [`GetObjectLockConfig`](#GetObjectLockConfig)                 |                                                       |
-| [`DeleteBucketTagging`](#DeleteBucketTagging)           | [`NewSourceInfo`](#NewSourceInfo)                                     |                                                   |                                               | [`EnableVersioning`](#EnableVersioning)                       |                                                       |
-|                                                         | [`NewDestinationInfo`](#NewDestinationInfo)                           |                                                   |                                               | [`DisableVersioning`](#DisableVersioning)                     |                                                       |
-|                                                         | [`PutObjectRetention`](#PutObjectRetention)                           |                                                   |                                               | [`GetBucketEncryption`](#GetBucketEncryption)                 |                                                       |
-|                                                         | [`GetObjectRetention`](#GetObjectRetention)                           |                                                   |                                               | [`DeleteBucketEncryption`](#DeleteBucketEncryption)           |                                                       |
-|                                                         | [`PutObjectLegalHold`](#PutObjectLegalHold)                           |                                                   |                                               |                                                               |                                                       |
-|                                                         | [`GetObjectLegalHold`](#GetObjectLegalHold)                           |                                                   |                                               |                                                               |                                                       |
-|                                                         | [`SelectObjectContent`](#SelectObjectContent)                         |                                                   |                                               |                                                               |                                                       |
-|                                                         | [`PutObjectTagging`](#PutObjectTagging)                               |                                                   |                                               |                                                               |                                                       |
-|                                                         | [`GetObjectTagging`](#GetObjectTagging)                               |                                                   |                                               |                                                               |                                                       |
-|                                                         | [`RemoveObjectTagging`](#RemoveObjectTagging)                         |                                                   |                                               |                                                               |                                                       |
-|                                                         |                                                                       |                                                   |                                               |                                                               |                                                       |
+| Bucket operations                                       | Object operations                                   | Encrypted Object operations        | Presigned operations                          | Bucket Policy/Notification Operations                         | Client custom settings                                |
+| :---                                                    | :---                                                | :---                               | :---                                          | :---                                                          | :---                                                  |
+| [`MakeBucket`](#MakeBucket)                             | [`GetObject`](#GetObject)                           | [`GetObject`](#GetObject)          | [`PresignedGetObject`](#PresignedGetObject)   | [`SetBucketPolicy`](#SetBucketPolicy)                         | [`SetAppInfo`](#SetAppInfo)                           |
+| [`PutObject`](#PutObject)                           | [`PutObject`](#PutObject)          | [`PresignedPutObject`](#PresignedPutObject)   | [`GetBucketPolicy`](#GetBucketPolicy)                         | [`SetCustomTransport`](#SetCustomTransport)           |
+| [`ListBuckets`](#ListBuckets)                           | [`CopyObject`](#CopyObject)                         | [`CopyObject`](#CopyObject)        | [`PresignedPostPolicy`](#PresignedPostPolicy) | [`SetBucketNotification`](#SetBucketNotification)             | [`TraceOn`](#TraceOn)                                 |
+| [`BucketExists`](#BucketExists)                         | [`StatObject`](#StatObject)                         | [`StatObject`](#StatObject)        |                                               | [`GetBucketNotification`](#GetBucketNotification)             | [`TraceOff`](#TraceOff)                               |
+| [`RemoveBucket`](#RemoveBucket)                         | [`RemoveObject`](#RemoveObject)                     | [`FPutObject`](#FPutObject)        |                                               | [`RemoveAllBucketNotification`](#RemoveAllBucketNotification) | [`SetS3TransferAccelerate`](#SetS3TransferAccelerate) |
+| [`ListObjects`](#ListObjects)                           | [`RemoveObjects`](#RemoveObjects)                   | [`FGetObject`](#FGetObject)        |                                               | [`ListenBucketNotification`](#ListenBucketNotification)       |                                                       |
+|                                                         | [`RemoveIncompleteUpload`](#RemoveIncompleteUpload) | [`ComposeObject`](#ComposeObjecet) |                                               | [`SetBucketLifecycle`](#SetBucketLifecycle)                   |                                                       |
+| [`ListIncompleteUploads`](#ListIncompleteUploads)       | [`FPutObject`](#FPutObject)                         |                                    |                                               | [`GetBucketLifecycle`](#GetBucketLifecycle)                   |                                                       |
+| [`SetBucketTagging`](#SetBucketTagging)                 | [`FGetObject`](#FGetObject)                         |                                    |                                               | [`SetObjectLockConfig`](#SetObjectLockConfig)                 |                                                       |
+| [`GetBucketTagging`](#GetBucketTagging)                 | [`ComposeObject`](#ComposeObject)                   |                                    |                                               | [`GetObjectLockConfig`](#GetObjectLockConfig)                 |                                                       |
+| [`DeleteBucketTagging`](#DeleteBucketTagging)           |                                                     |                                    |                                               | [`EnableVersioning`](#EnableVersioning)                       |                                                       |
+|                                                         |                                                     |                                    |                                               | [`DisableVersioning`](#DisableVersioning)                     |                                                       |
+|                                                         | [`PutObjectRetention`](#PutObjectRetention)         |                                    |                                               | [`GetBucketEncryption`](#GetBucketEncryption)                 |                                                       |
+|                                                         | [`GetObjectRetention`](#GetObjectRetention)         |                                    |                                               | [`DeleteBucketEncryption`](#DeleteBucketEncryption)           |                                                       |
+|                                                         | [`PutObjectLegalHold`](#PutObjectLegalHold)         |                                    |                                               |                                                               |                                                       |
+|                                                         | [`GetObjectLegalHold`](#GetObjectLegalHold)         |                                    |                                               |                                                               |                                                       |
+|                                                         | [`SelectObjectContent`](#SelectObjectContent)       |                                    |                                               |                                                               |                                                       |
+|                                                         | [`PutObjectTagging`](#PutObjectTagging)             |                                    |                                               |                                                               |                                                       |
+|                                                         | [`GetObjectTagging`](#GetObjectTagging)             |                                    |                                               |                                                               |                                                       |
+|                                                         | [`RemoveObjectTagging`](#RemoveObjectTagging)       |                                    |                                               |                                                               |                                                       |
+|                                                         |                                                     |                                    |                                               |                                                               |                                                       |
 
 ## 1. Constructor
 <a name="MinIO"></a>
@@ -82,12 +82,12 @@ Initializes a new client object.
 
 __Parameters__
 
-|Param   |Type   |Description   |
-|:---|:---| :---|
-|`endpoint`   | _string_  |S3 compatible object storage endpoint   |
-|`accessKeyID`  |_string_   |Access key for the object storage |
-|`secretAccessKey`  | _string_  |Secret key for the object storage |
-|`ssl`   | _bool_  | If 'true' API requests will be secure (HTTPS), and insecure (HTTP) otherwise  |
+| Param             | Type     | Description                                                                  |
+|:------------------|:---------|:-----------------------------------------------------------------------------|
+| `endpoint`        | _string_ | S3 compatible object storage endpoint                                        |
+| `accessKeyID`     | _string_ | Access key for the object storage                                            |
+| `secretAccessKey` | _string_ | Secret key for the object storage                                            |
+| `ssl`             | _bool_   | If 'true' API requests will be secure (HTTPS), and insecure (HTTP) otherwise |
 
 ### NewWithRegion(endpoint, accessKeyID, secretAccessKey string, ssl bool, region string) (*Client, error)
 Initializes minio client, with region configured. Unlike New(), NewWithRegion avoids bucket-location lookup operations and it is slightly faster. Use this function when your application deals with a single region.
@@ -97,99 +97,66 @@ Initializes minio client with options configured.
 
 __Parameters__
 
-|Param   |Type   |Description   |
-|:---|:---| :---|
-|`endpoint`   | _string_  |S3 compatible object storage endpoint |
-|`opts`  |_minio.Options_   | Options for constructing a new client|
+| Param      | Type            | Description                           |
+|:-----------|:----------------|:--------------------------------------|
+| `endpoint` | _string_        | S3 compatible object storage endpoint |
+| `opts`     | _minio.Options_ | Options for constructing a new client |
 
 __minio.Options__
 
-|Field | Type | Description |
-|:--- |:--- | :--- |
-| `opts.Creds` | _*credentials.Credentials_ | Access Credentials|
-| `opts.Secure` | _bool_ | If 'true' API requests will be secure (HTTPS), and insecure (HTTP) otherwise |
-| `opts.Region` | _string_ | region |
-| `opts.BucketLookup` | _BucketLookupType_ | Bucket lookup type can be one of the following values |
-| |  | _minio.BucketLookupDNS_ |
-| |  | _minio.BucketLookupPath_ |
-| |  | _minio.BucketLookupAuto_ |
+| Field               | Type                       | Description                                                                  |
+|:--------------------|:---------------------------|:-----------------------------------------------------------------------------|
+| `opts.Creds`        | _*credentials.Credentials_ | Access Credentials                                                           |
+| `opts.Secure`       | _bool_                     | If 'true' API requests will be secure (HTTPS), and insecure (HTTP) otherwise |
+| `opts.Region`       | _string_                   | region                                                                       |
+| `opts.BucketLookup` | _BucketLookupType_         | Bucket lookup type can be one of the following values                        |
+|                     |                            | _minio.BucketLookupDNS_                                                      |
+|                     |                            | _minio.BucketLookupPath_                                                     |
+|                     |                            | _minio.BucketLookupAuto_                                                     |
 ## 2. Bucket operations
 
 <a name="MakeBucket"></a>
-### MakeBucket(ctx context.Context, bucketName, location string) error
+### MakeBucket(ctx context.Context, bucketName string, opts MakeBucketOptions)
 Creates a new bucket.
 
 __Parameters__
 
-| Param  | Type  | Description  |
-|---|---|---|
-|`ctx`  | _context.Context_  | Custom context for timeout/cancellation of the call|
-|`bucketName`  | _string_  | Name of the bucket |
-| `location`  |  _string_ | Region where the bucket is to be created. Default value is us-east-1. Other valid values are listed below. Note: When used with minio server, use the region specified in its config file (defaults to us-east-1).|
-| | |us-east-1 |
-| | |us-east-2 |
-| | |us-west-1 |
-| | |us-west-2 |
-| | |ca-central-1 |
-| | |eu-west-1 |
-| | |eu-west-2 |
-| | |eu-west-3 |
-| | | eu-central-1|
-| | | eu-north-1|
-| | | ap-east-1|
-| | | ap-south-1|
-| | | ap-southeast-1|
-| | | ap-southeast-2|
-| | | ap-northeast-1|
-| | | ap-northeast-2|
-| | | ap-northeast-3|
-| | | me-south-1|
-| | | sa-east-1|
-| | | us-gov-west-1|
-| | | us-gov-east-1|
-| | | cn-north-1|
-| | | cn-northwest-1|
-
-
-__Example__
-
-
-```go
-err = minioClient.MakeBucket(context.Background(), "mybucket", "us-east-1")
-if err != nil {
-    fmt.Println(err)
-    return
-}
-fmt.Println("Successfully created mybucket.")
-```
-
-<a name="MakeBucketWithObjectLock"></a>
-### MakeBucketWithObjectLock(ctx context.Background, bucketName, location string) error
-Creates a new bucket with object lock enabled.
-
-__Parameters__
-
-| Param  | Type  | Description  |
-|---|---|---|
-|`ctx`  | _context.Context_  | Custom context for timeout/cancellation of the call|
-|`bucketName`  | _string_  | Name of the bucket |
-| `location`  |  _string_ | Region where the bucket is to be created. Default value is us-east-1. Other valid values are listed below. Note: When used with minio server, use the region specified in its config file (defaults to us-east-1).|
-| | |us-east-1 |
-| | |us-west-1 |
-| | |us-west-2 |
-| | |eu-west-1 |
-| | | eu-central-1|
-| | | ap-southeast-1|
-| | | ap-northeast-1|
-| | | ap-southeast-2|
-| | | sa-east-1|
+| Param        | Type                      | Description                                                                                                                                                                                                        |
+|--------------|---------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `ctx`        | _context.Context_         | Custom context for timeout/cancellation of the call                                                                                                                                                                |
+| `bucketName` | _string_                  | Name of the bucket                                                                                                                                                                                                 |
+| `opts`       | _minio.MakeBucketOptions_ | Bucket options such as `Region` where the bucket is to be created. Default value is us-east-1. Other valid values are listed below. Note: When used with minio server, use the region specified in its config file (defaults to us-east-1). |
+|              |                           | us-east-1                                                                                                                                                                                                          |
+|              |                           | us-east-2                                                                                                                                                                                                          |
+|              |                           | us-west-1                                                                                                                                                                                                          |
+|              |                           | us-west-2                                                                                                                                                                                                          |
+|              |                           | ca-central-1                                                                                                                                                                                                       |
+|              |                           | eu-west-1                                                                                                                                                                                                          |
+|              |                           | eu-west-2                                                                                                                                                                                                          |
+|              |                           | eu-west-3                                                                                                                                                                                                          |
+|              |                           | eu-central-1                                                                                                                                                                                                       |
+|              |                           | eu-north-1                                                                                                                                                                                                         |
+|              |                           | ap-east-1                                                                                                                                                                                                          |
+|              |                           | ap-south-1                                                                                                                                                                                                         |
+|              |                           | ap-southeast-1                                                                                                                                                                                                     |
+|              |                           | ap-southeast-2                                                                                                                                                                                                     |
+|              |                           | ap-northeast-1                                                                                                                                                                                                     |
+|              |                           | ap-northeast-2                                                                                                                                                                                                     |
+|              |                           | ap-northeast-3                                                                                                                                                                                                     |
+|              |                           | me-south-1                                                                                                                                                                                                         |
+|              |                           | sa-east-1                                                                                                                                                                                                          |
+|              |                           | us-gov-west-1                                                                                                                                                                                                      |
+|              |                           | us-gov-east-1                                                                                                                                                                                                      |
+|              |                           | cn-north-1                                                                                                                                                                                                         |
+|              |                           | cn-northwest-1                                                                                                                                                                                                     |
 
 
 __Example__
 
 
 ```go
-err = minioClient.MakeBucketWithObjectLock(context.Background(), "mybucket", "us-east-1")
+// Create a bucket at region 'us-east-1' with object locking enabled.
+err = minioClient.MakeBucket(context.Background(), "mybucket", minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
 if err != nil {
     fmt.Println(err)
     return
@@ -288,19 +255,17 @@ if err != nil {
 ```
 
 <a name="ListObjects"></a>
-### ListObjects(ctx context.Context, bucketName, prefix string, recursive bool, doneCh chan struct{}) <-chan ObjectInfo
+### ListObjects(ctx context.Context, bucketName string, opts ListObjectsOptions) <-chan ObjectInfo
 Lists objects in a bucket.
 
 __Parameters__
 
 
-|Param   |Type   |Description   |
-|:---|:---| :---|
-|`ctx`  | _context.Context_  | Custom context for timeout/cancellation of the call|
-|`bucketName` | _string_  |Name of the bucket   |
-|`objectPrefix` |_string_   | Prefix of objects to be listed |
-|`recursive`  | _bool_  |`true` indicates recursive style listing and `false` indicates directory style listing delimited by '/'.  |
-|`doneCh`  | _chan struct{}_ | A message on this channel ends the ListObjects iterator.  |
+| Param        | Type                       | Description                                         |
+|:-------------|:---------------------------|:----------------------------------------------------|
+| `ctx`        | _context.Context_          | Custom context for timeout/cancellation of the call |
+| `bucketName` | _string_                   | Name of the bucket                                  |
+| `opts`       | _minio.ListObjectsOptions_ | Options per to list objects                    |
 
 
 __Return Value__
@@ -320,14 +285,14 @@ __minio.ObjectInfo__
 
 
 ```go
-// Create a done channel to control 'ListObjects' go routine.
-doneCh := make(chan struct{})
+ctx, cancel := context.WithCancel(context.Background())
 
-// Indicate to our routine to exit cleanly upon return.
-defer close(doneCh)
+defer cancel()
 
-isRecursive := true
-objectCh := minioClient.ListObjects(context.Background(), "mybucket", "myprefix", isRecursive, doneCh)
+objectCh := minioClient.ListObjects(ctx, "mybucket", ListObjectOptions{
+       Prefix: "myprefix",
+       Recursive: true,
+})
 for object := range objectCh {
     if object.Err != nil {
         fmt.Println(object.Err)
@@ -338,47 +303,6 @@ for object := range objectCh {
 ```
 
 
-<a name="ListObjectsV2"></a>
-### ListObjectsV2(ctx context.Context, bucketName, prefix string, recursive bool, doneCh chan struct{}) <-chan ObjectInfo
-Lists objects in a bucket using the recommended listing API v2
-
-__Parameters__
-
-
-|Param   |Type   |Description   |
-|:---|:---| :---|
-|`ctx`  | _context.Context_  | Custom context for timeout/cancellation of the call|
-|`bucketName`  | _string_  |Name of the bucket |
-| `objectPrefix` |_string_   | Prefix of objects to be listed |
-| `recursive`  | _bool_  |`true` indicates recursive style listing and `false` indicates directory style listing delimited by '/'.  |
-|`doneCh`  | _chan struct{}_ | A message on this channel ends the ListObjectsV2 iterator.  |
-
-
-__Return Value__
-
-|Param   |Type   |Description   |
-|:---|:---| :---|
-|`objectInfo`  | _chan minio.ObjectInfo_ |Read channel for all the objects in the bucket, the object is of the format listed below: |
-
-
-```go
-// Create a done channel to control 'ListObjectsV2' go routine.
-doneCh := make(chan struct{})
-
-// Indicate to our routine to exit cleanly upon return.
-defer close(doneCh)
-
-isRecursive := true
-objectCh := minioClient.ListObjectsV2(context.Background(), "mybucket", "myprefix", isRecursive, doneCh)
-for object := range objectCh {
-    if object.Err != nil {
-        fmt.Println(object.Err)
-        return
-    }
-    fmt.Println(object)
-}
-```
-
 <a name="ListIncompleteUploads"></a>
 ### ListIncompleteUploads(ctx context.Context, bucketName, prefix string, recursive bool) <- chan ObjectMultipartInfo
 Lists partially uploaded objects in a bucket.
@@ -387,12 +311,12 @@ Lists partially uploaded objects in a bucket.
 __Parameters__
 
 
-|Param   |Type   |Description   |
-|:---|:---| :---|
-|`ctx`  | _context.Context_  | Custom context for timeout/cancellation of the call|
-|`bucketName`  | _string_  |Name of the bucket |
-| `prefix` |_string_   | Prefix of objects that are partially uploaded |
-| `recursive`  | _bool_  |`true` indicates recursive style listing and `false` indicates directory style listing delimited by '/'.  |
+| Param        | Type              | Description                                                                                              |
+|:-------------|:------------------|:---------------------------------------------------------------------------------------------------------|
+| `ctx`        | _context.Context_ | Custom context for timeout/cancellation of the call                                                      |
+| `bucketName` | _string_          | Name of the bucket                                                                                       |
+| `prefix`     | _string_          | Prefix of objects that are partially uploaded                                                            |
+| `recursive`  | _bool_            | `true` indicates recursive style listing and `false` indicates directory style listing delimited by '/'. |
 
 
 __Return Value__
@@ -430,11 +354,11 @@ Sets tags to a bucket.
 
 
 __Parameters__
-| Param        | Type         | Description        |
-|:-------------|:-------------|:-------------------|
-|`ctx`  | _context.Context_  | Custom context for timeout/cancellation of the call|
-| `bucketName` | _string_     | Name of the bucket |
-| `tags`       | _*tags.Tags_ | Bucket tags        |
+| Param        | Type              | Description                                         |
+|:-------------|:------------------|:----------------------------------------------------|
+| `ctx`        | _context.Context_ | Custom context for timeout/cancellation of the call |
+| `bucketName` | _string_          | Name of the bucket                                  |
+| `tags`       | _*tags.Tags_      | Bucket tags                                         |
 
 __Example__
 ```go
@@ -653,29 +577,26 @@ API methods PutObjectWithSize, PutObjectWithMetadata, PutObjectStreaming, and Pu
 
 
 <a name="CopyObject"></a>
-### CopyObject(ctx context.Context, dst DestinationInfo, src SourceInfo) (UploadInfo, error)
-Create or replace an object through server-side copying of an existing object. It supports conditional copying, copying a part of an object and server-side encryption of destination and decryption of source. See the `SourceInfo` and `DestinationInfo` types for further details.
+### CopyObject(ctx context.Context, dst CopyDestOptions, src CopySrcOptions) (UploadInfo, error)
+Create or replace an object through server-side copying of an existing object. It supports conditional copying, copying a part of an object and server-side encryption of destination and decryption of source. See the `CopySrcOptions` and `DestinationInfo` types for further details.
 
 To copy multiple source objects into a single destination object see the `ComposeObject` API.
 
 __Parameters__
 
-
-|Param   |Type   |Description   |
-|:---|:---| :---|
-|`ctx`  | _context.Context_  | Custom context for timeout/cancellation of the call|
-|`dst`  | _minio.DestinationInfo_  |Argument describing the destination object |
-|`src` | _minio.SourceInfo_  |Argument describing the source object |
+| Param | Type                    | Description                                         |
+|:------|:------------------------|:----------------------------------------------------|
+| `ctx` | _context.Context_       | Custom context for timeout/cancellation of the call |
+| `dst` | _minio.CopyDestOptions_ | Argument describing the destination object          |
+| `src` | _minio.CopySrcOptions_  | Argument describing the source object               |
 
 
 __minio.UploadInfo__
 
-| Field               | Type     | Description                                                                                                                                                                        |
-|:--------------------|:---------|:-------------------------------------------|
-| `info.ETag`         | _string_ | The ETag of the new object                 |
-| `info.VersionID`    | _string_ | The version identifyer of the new object   |
-
-
+| Field            | Type     | Description                              |
+|:-----------------|:---------|:-----------------------------------------|
+| `info.ETag`      | _string_ | The ETag of the new object               |
+| `info.VersionID` | _string_ | The version identifyer of the new object |
 
 
 __Example__
@@ -684,13 +605,15 @@ __Example__
 ```go
 // Use-case 1: Simple copy object with no conditions.
 // Source object
-src := minio.NewSourceInfo("my-sourcebucketname", "my-sourceobjectname", nil)
+srcOpts := minio.CopySrcOptions{
+    Bucket: "my-sourcebucketname",
+    Object: "my-sourceobjectname",
+}
 
 // Destination object
-dst, err := minio.NewDestinationInfo("my-bucketname", "my-objectname", nil, nil)
-if err != nil {
-    fmt.Println(err)
-    return
+dstOpts := minio.CopyDestOptions{
+    Bucket: "my-bucketname",
+    Object: "my-objectname",
 }
 
 // Copy object call
@@ -712,25 +635,21 @@ fmt.Println("Successfully copied object:", uploadInfo)
 // 4. copy only first 1MiB of object.
 
 // Source object
-src := minio.NewSourceInfo("my-sourcebucketname", "my-sourceobjectname", nil)
-
-// Set matching ETag condition, copy object which matches the following ETag.
-src.SetMatchETagCond("31624deb84149d2f8ef9c385918b653a")
-
-// Set modified condition, copy object modified since 2014 April 1.
-src.SetModifiedSinceCond(time.Date(2014, time.April, 1, 0, 0, 0, 0, time.UTC))
-
-// Set unmodified condition, copy object unmodified since 2014 April 23.
-src.SetUnmodifiedSinceCond(time.Date(2014, time.April, 23, 0, 0, 0, 0, time.UTC))
+srcOpts := minio.CopySrcOptions{
+    Bucket: "my-sourcebucketname",
+    Object: "my-sourceobjectname",
+    MatchETag: "31624deb84149d2f8ef9c385918b653a",
+    MatchModifiedSince: time.Date(2014, time.April, 1, 0, 0, 0, 0, time.UTC),
+    MatchUnmodifiedSince: time.Date(2014, time.April, 23, 0, 0, 0, 0, time.UTC),
+    Start: 0,
+    End: 1024*1024-1,
+}
 
-// Set copy-range of only first 1MiB of file.
-src.SetRange(0, 1024*1024-1)
 
 // Destination object
-dst, err := minio.NewDestinationInfo("my-bucketname", "my-objectname", nil, nil)
-if err != nil {
-    fmt.Println(err)
-    return
+dstOpts := minio.CopyDestOptions{
+    Bucket: "my-bucketname",
+    Object: "my-objectname",
 }
 
 // Copy object call
@@ -745,17 +664,17 @@ fmt.Println("Successfully copied object:", uploadInfo)
 ```
 
 <a name="ComposeObject"></a>
-### ComposeObject(ctx context.Context, dst minio.DestinationInfo, srcs []minio.SourceInfo) (UploadInfo, error)
+### ComposeObject(ctx context.Context, dst minio.CopyDestOptions, srcs ...minio.CopySrcOptions) (UploadInfo, error)
 Create an object by concatenating a list of source objects using server-side copying.
 
 __Parameters__
 
 
-|Param   |Type   |Description   |
-|:---|:---|:---|
-|`ctx`  | _context.Context_  | Custom context for timeout/cancellation of the call|
-|`dst`  | _minio.DestinationInfo_  |Struct with info about the object to be created. |
-|`srcs` | _[]minio.SourceInfo_  |Slice of struct with info about source objects to be concatenated in order. |
+| Param  | Type                      | Description                                                                 |
+|:-------|:--------------------------|:----------------------------------------------------------------------------|
+| `ctx`  | _context.Context_         | Custom context for timeout/cancellation of the call                         |
+| `dst`  | _minio.CopyDestOptions_   | Struct with info about the object to be created.                            |
+| `srcs` | _...minio.CopySrcOptions_ | Slice of struct with info about source objects to be concatenated in order. |
 
 
 __minio.UploadInfo__
@@ -766,10 +685,8 @@ __minio.UploadInfo__
 | `info.VersionID`    | _string_ | The version identifyer of the new object   |
 
 
-
 __Example__
 
-
 ```go
 // Prepare source decryption key (here we assume same key to
 // decrypt all source objects.)
@@ -777,30 +694,39 @@ sseSrc := encrypt.DefaultPBKDF([]byte("password"), []byte("salt"))
 
 // Source objects to concatenate. We also specify decryption
 // key for each
-src1 := minio.NewSourceInfo("bucket1", "object1", sseSrc)
-src1.SetMatchETagCond("31624deb84149d2f8ef9c385918b653a")
-
-src2 := minio.NewSourceInfo("bucket2", "object2", sseSrc)
-src2.SetMatchETagCond("f8ef9c385918b653a31624deb84149d2")
+src1Opts := minio.CopySrcOptions{
+    Bucket: "bucket1",
+    Object: "object1",
+    Encryption: sseSrc,
+    MatchETag: "31624deb84149d2f8ef9c385918b653a",
+}
 
-src3 := minio.NewSourceInfo("bucket3", "object3", sseSrc)
-src3.SetMatchETagCond("5918b653a31624deb84149d2f8ef9c38")
+src2Opts := minio.CopySrcOptions{
+    Bucket: "bucket2",
+    Object: "object2",
+    Encryption: sseSrc,
+    MatchETag: "f8ef9c385918b653a31624deb84149d2",
+}
 
-// Create slice of sources.
-srcs := []minio.SourceInfo{src1, src2, src3}
+src3Opts := minio.CopySrcOptions{
+    Bucket: "bucket3",
+    Object: "object3",
+    Encryption: sseSrc,
+    MatchETag: "5918b653a31624deb84149d2f8ef9c38",
+}
 
 // Prepare destination encryption key
 sseDst := encrypt.DefaultPBKDF([]byte("new-password"), []byte("new-salt"))
 
 // Create destination info
-dst, err := minio.NewDestinationInfo("bucket", "object", sseDst, nil)
-if err != nil {
-    fmt.Println(err)
-    return
+dstOpts := CopyDestOptions{
+    Bucket: "bucket",
+    Object: "object",
+    Encryption: sseDst,
 }
 
 // Compose object call by concatenating multiple source files.
-uploadInfo, err := minioClient.ComposeObject(context.Background(), dst, srcs)
+uploadInfo, err := minioClient.ComposeObject(context.Background(), dst, srcs...)
 if err != nil {
     fmt.Println(err)
     return
@@ -809,184 +735,6 @@ if err != nil {
 fmt.Println("Composed object successfully:", uploadInfo)
 ```
 
-<a name="NewSourceInfo"></a>
-### NewSourceInfo(bucket, object string, decryptSSEC *SSEInfo) SourceInfo
-Construct a `SourceInfo` object that can be used as the source for server-side copying operations like `CopyObject` and `ComposeObject`. This object can be used to set copy-conditions on the source.
-
-__Parameters__
-
-| Param         | Type             | Description                                                      |
-| :---          | :---             | :---                                                             |
-| `bucket`      | _string_         | Name of the source bucket                                        |
-| `object`      | _string_         | Name of the source object                                        |
-| `sse` | _*encrypt.ServerSide_ | Interface provided by `encrypt` package to specify server-side-encryption. (For more information see https://godoc.org/github.com/minio/minio-go/v7) |
-
-__Example__
-
-```go
-// No decryption parameter.
-src := minio.NewSourceInfo("bucket", "object", nil)
-
-// Destination object
-dst, err := minio.NewDestinationInfo("my-bucketname", "my-objectname", nil, nil)
-if err != nil {
-    fmt.Println(err)
-    return
-}
-
-// Copy object call
-err = minioClient.CopyObject(dst, src)
-if err != nil {
-    fmt.Println(err)
-    return
-}
-```
-
-```go
-// With decryption parameter.
-sseSrc := encrypt.DefaultPBKDF([]byte("password"), []byte("salt"))
-src := minio.NewSourceInfo("bucket", "object", sseSrc)
-
-// Destination object
-dst, err := minio.NewDestinationInfo("my-bucketname", "my-objectname", nil, nil)
-if err != nil {
-    fmt.Println(err)
-    return
-}
-
-// Copy object call
-_, err = minioClient.CopyObject(dst, src)
-if err != nil {
-    fmt.Println(err)
-    return
-}
-```
-
-<a name="NewDestinationInfo"></a>
-### NewDestinationInfo(bucket, object string, encryptSSEC *SSEInfo, userMeta map[string]string) (DestinationInfo, error)
-Construct a `DestinationInfo` object that can be used as the destination object for server-side copying operations like `CopyObject` and `ComposeObject`.
-
-__Parameters__
-
-| Param         | Type                | Description                                                                                                    |
-| :---          | :---                | :---                                                                                                           |
-| `bucket`      | _string_            | Name of the destination bucket                                                                                 |
-| `object`      | _string_            | Name of the destination object                                                                                 |
-| `sse` | _*encrypt.ServerSide_ | Interface provided by `encrypt` package to specify server-side-encryption. (For more information see https://godoc.org/github.com/minio/minio-go/v7) |                                              |
-| `userMeta`    | _map[string]string_ | User metadata to be set on the destination. If nil, with only one source, user-metadata is copied from source. |
-
-__Example__
-
-```go
-// No encryption parameter.
-src := minio.NewSourceInfo("bucket", "object", nil)
-dst, err := minio.NewDestinationInfo("bucket", "object", nil, nil)
-if err != nil {
-    fmt.Println(err)
-    return
-}
-
-// Copy object call
-_, err = minioClient.CopyObject(dst, src)
-if err != nil {
-    fmt.Println(err)
-    return
-}
-```
-
-```go
-src := minio.NewSourceInfo("bucket", "object", nil)
-
-// With encryption parameter.
-sseDst := encrypt.DefaultPBKDF([]byte("password"), []byte("salt"))
-dst, err := minio.NewDestinationInfo("bucket", "object", sseDst, nil)
-if err != nil {
-    fmt.Println(err)
-    return
-}
-
-// Copy object call
-_, err = minioClient.CopyObject(dst, src)
-if err != nil {
-    fmt.Println(err)
-    return
-}
-```
-
-<a name="NewDestinationInfo"></a>
-### NewDestinationInfo(bucket, object string, destOpts DestInfoOptions) (DestinationInfo, error)
-Construct a `DestinationInfo` object that can be used as the destination object for server-side copying operations like `CopyObject` and `ComposeObject`.
-
-__Parameters__
-
-| Param         | Type                | Description                                                                                                    |
-| :---          | :---                | :---                                                                                                           |
-| `bucket`      | _string_            | Name of the destination bucket                                                                                 |
-| `object`      | _string_            | Name of the destination object                                                                                 |
-| `destOpts`    | _minio.DestInfoOptions_   | Pointer to struct that allows user to set optional custom metadata, user tags, and server side encryption parameters. |
-
-__minio.DestInfoOptions__
-
-|Field | Type | Description |
-|:--- |:--- | :--- |
-| `destOpts.Encryption` | _encrypt.ServerSide_ | Interface provided by encrypt package to specify server-side-encryption. (For more information see https://godoc.org/github.com/minio/minio-go/v7). |
-| `destOpts.UserMetadata` | _map[string]string_ | Map of user meta data to be set on destination object. |
-| `destOpts.UserTags` | _map[string]string_ | Map of user object tags to be set on destination object. |
-| `destOpts.ReplaceTags` | _bool_ | Replace object tags of the destination object. |
-| `destOpts.LegalHold` | _minio.LegalHoldStatus_ | LegalHold(En|Dis)abled. |
-| `destOpts.Mode` | _minio.RetentionMode_ | Retention mode to be set on copied object. |
-| `destOpts.RetainUntilDate` | _time.Time_ | Time until object retention should be applied on copied object. |
-
-__Example__
-
-```go
-// No encryption parameter.
-src := minio.NewSourceInfo("bucket", "object", nil)
-tags := map[string]string{
-    "Tag1": "Value1",
-    "Tag2": "Value2",
-}
-dst, err := minio.NewDestinationInfo("bucket", "object", minio.DestInfoOptions{
-    UserTags: tags, ReplaceTags: true,
-})
-if err != nil {
-    fmt.Println(err)
-    return
-}
-
-// Copy object call
-_, err = minioClient.CopyObject(context.Background(), dst, src)
-if err != nil {
-    fmt.Println(err)
-    return
-}
-```
-
-```go
-src := minio.NewSourceInfo("bucket", "object", nil)
-
-// With encryption parameter.
-sseDst := encrypt.DefaultPBKDF([]byte("password"), []byte("salt"))
-tags := map[string]string{
-    "Tag1": "Value1",
-    "Tag2": "Value2",
-}
-dst, err := minio.NewDestinationInfo("bucket", "object", minio.DestInfoOptions{
-    Encryption: sseDst, UserTags: tags, ReplaceTags: true,
-})
-if err != nil {
-    fmt.Println(err)
-    return
-}
-
-// Copy object call
-_, err = minioClient.CopyObject(context.Background(), dst, src)
-if err != nil {
-    fmt.Println(err)
-    return
-}
-```
-
 <a name="FPutObject"></a>
 ### FPutObject(ctx context.Context, bucketName, objectName, filePath, opts PutObjectOptions) (info UploadInfo, err error)
 Uploads contents from a file to objectName.
@@ -1161,7 +909,7 @@ go func() {
 opts := minio.RemoveObjectsOptions{
 	GovernanceBypass: true,
 }
-    
+
 for rErr := range minioClient.RemoveObjects(context.Background(), "my-bucketname", objectsCh, opts) {
     fmt.Println("Error detected during deletion: ", rErr)
 }
diff --git a/examples/s3/composeobject.go b/examples/s3/composeobject.go
index 33ee039..5798ca3 100644
--- a/examples/s3/composeobject.go
+++ b/examples/s3/composeobject.go
@@ -2,7 +2,7 @@
 
 /*
  * MinIO Go Library for Amazon S3 Compatible Cloud Storage
- * Copyright 2015-2017 MinIO, Inc.
+ * Copyright 2020 MinIO, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -50,14 +50,26 @@ func main() {
 
 	// Source objects to concatenate. We also specify decryption
 	// key for each
-	src1 := minio.NewSourceInfo("bucket1", "object1", decKey)
-	src1.SetMatchETagCond("31624deb84149d2f8ef9c385918b653a")
+	src1 := minio.CopySrcOptions{
+		Bucket:     "bucket1",
+		Object:     "object1",
+		Encryption: decKey,
+		MatchETag:  "31624deb84149d2f8ef9c385918b653a",
+	}
 
-	src2 := minio.NewSourceInfo("bucket2", "object2", decKey)
-	src2.SetMatchETagCond("f8ef9c385918b653a31624deb84149d2")
+	src2 := minio.CopySrcOptions{
+		Bucket:     "bucket2",
+		Object:     "object2",
+		Encryption: decKey,
+		MatchETag:  "f8ef9c385918b653a31624deb84149d2",
+	}
 
-	src3 := minio.NewSourceInfo("bucket3", "object3", decKey)
-	src3.SetMatchETagCond("5918b653a31624deb84149d2f8ef9c38")
+	src3 := minio.CopySrcOptions{
+		Bucket:     "bucket3",
+		Object:     "object3",
+		Encryption: decKey,
+		MatchETag:  "5918b653a31624deb84149d2f8ef9c38",
+	}
 
 	// Create slice of sources.
 	srcs := []minio.SourceInfo{src1, src2, src3}
@@ -66,12 +78,13 @@ func main() {
 	encKey, _ := encrypt.NewSSEC([]byte{8, 9, 0})
 
 	// Create destination info
-	dst, err := minio.NewDestinationInfo("bucket", "object", minio.DestInfoOptions{Encryption: encKey})
-	if err != nil {
-		log.Fatalln(err)
+	dst := minio.CopyDestOptions{
+		Bucket:     "bucket",
+		Object:     "object",
+		Encryption: encKey,
 	}
 
-	uploadInfo, err := s3Client.ComposeObject(context.Background(), dst, srcs)
+	uploadInfo, err := s3Client.ComposeObject(context.Background(), dst, srcs...)
 	if err != nil {
 		log.Fatalln(err)
 	}
diff --git a/examples/s3/copyobject-with-new-tags.go b/examples/s3/copyobject-with-new-tags.go
index 744fce5..2cb3dcc 100644
--- a/examples/s3/copyobject-with-new-tags.go
+++ b/examples/s3/copyobject-with-new-tags.go
@@ -45,39 +45,30 @@ func main() {
 	// s3Client.TraceOn(os.Stderr)
 
 	// Source object
-	src := minio.NewSourceInfo("my-sourcebucketname", "my-sourceobjectname", nil)
-
-	// All following conditions are allowed and can be combined together.
-
-	// Set modified condition, copy object modified since 2014 April.
-	src.SetModifiedSinceCond(time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC))
-
-	// Set unmodified condition, copy object unmodified since 2014 April.
-	// src.SetUnmodifiedSinceCond(time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC))
-
-	// Set matching ETag condition, copy object which matches the following ETag.
-	// src.SetMatchETagCond("31624deb84149d2f8ef9c385918b653a")
-
-	// Set matching ETag except condition, copy object which does not match the following ETag.
-	// src.SetMatchETagExceptCond("31624deb84149d2f8ef9c385918b653a")
-
-	tags := map[string]string{
-		"Tag1": "Value1",
-		"Tag2": "Value2",
+	src := minio.CopySrcOptions{
+		Bucket: "my-sourcebucketname",
+		Object: "my-sourceobjectname",
+		// All following conditions are allowed and can be combined together.
+		// Set modified condition, copy object modified since 2014 April.
+		MatchModifiedSince: time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC),
 	}
 
 	// Destination object
-	dst, err := minio.NewDestinationInfo("my-bucketname", "my-objectname", minio.DestInfoOptions{
-		UserTags: tags, ReplaceTags: true,
-	})
-	if err != nil {
-		log.Fatalln(err)
+	dst := minio.CopyDestOptions{
+		Bucket:      "my-bucketname",
+		Object:      "my-objectname",
+		ReplaceTags: true,
+		UserTags: map[string]string{
+			"Tag1": "Value1",
+			"Tag2": "Value2",
+		},
 	}
 
 	// Initiate copy object.
-	_, err = s3Client.CopyObject(context.Background(), dst, src)
+	ui, err := s3Client.CopyObject(context.Background(), dst, src)
 	if err != nil {
 		log.Fatalln(err)
 	}
-	log.Println("Copied source object /my-sourcebucketname/my-sourceobjectname to destination /my-bucketname/my-objectname Successfully.")
+
+	log.Printf("Copied %s, successfully to %s - UploadInfo %v\n", dst, src, ui)
 }
diff --git a/examples/s3/copyobject.go b/examples/s3/copyobject.go
index a235a91..6499b98 100644
--- a/examples/s3/copyobject.go
+++ b/examples/s3/copyobject.go
@@ -2,7 +2,7 @@
 
 /*
  * MinIO Go Library for Amazon S3 Compatible Cloud Storage
- * Copyright 2015-2017 MinIO, Inc.
+ * Copyright 2020 MinIO, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -45,32 +45,31 @@ func main() {
 	// s3Client.TraceOn(os.Stderr)
 
 	// Source object
-	src := minio.NewSourceInfo("my-sourcebucketname", "my-sourceobjectname", nil)
-
-	// All following conditions are allowed and can be combined together.
-
-	// Set modified condition, copy object modified since 2014 April.
-	src.SetModifiedSinceCond(time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC))
-
-	// Set unmodified condition, copy object unmodified since 2014 April.
-	// src.SetUnmodifiedSinceCond(time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC))
-
-	// Set matching ETag condition, copy object which matches the following ETag.
-	// src.SetMatchETagCond("31624deb84149d2f8ef9c385918b653a")
-
-	// Set matching ETag except condition, copy object which does not match the following ETag.
-	// src.SetMatchETagExceptCond("31624deb84149d2f8ef9c385918b653a")
+	src := minio.CopySrcOptions{
+		Bucket: "my-sourcebucketname",
+		Object: "my-sourceobjectname",
+		// All following conditions are allowed and can be combined together.
+		// Set modified condition, copy object modified since 2014 April.
+		MatchModifiedSince: time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC),
+		// Set unmodified condition, copy object unmodified since 2014 April.
+		// MatchUnmodifiedSince: time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC),
+		// Set matching ETag condition, copy object which matches the following ETag.
+		// MatchETag: "31624deb84149d2f8ef9c385918b653a",
+		// Set matching ETag copy object which does not match the following ETag.
+		// NoMatchETag: "31624deb84149d2f8ef9c385918b653a",
+	}
 
 	// Destination object
-	dst, err := minio.NewDestinationInfo("my-bucketname", "my-objectname", minio.DestInfoOptions{})
-	if err != nil {
-		log.Fatalln(err)
+	dst := minio.CopyDestOptions{
+		Bucket: "my-bucketname",
+		Object: "my-objectname",
 	}
 
 	// Initiate copy object.
-	_, err = s3Client.CopyObject(context.Background(), dst, src)
+	ui, err = s3Client.CopyObject(context.Background(), dst, src)
 	if err != nil {
 		log.Fatalln(err)
 	}
-	log.Println("Copied source object /my-sourcebucketname/my-sourceobjectname to destination /my-bucketname/my-objectname Successfully.")
+
+	log.Printf("Copied %s, successfully to %s - UploadInfo %v\n", dst, src, ui)
 }
diff --git a/examples/s3/makebucketwithobjectlock.go b/examples/s3/makebucketwithobjectlock.go
deleted file mode 100644
index e1fdcdf..0000000
--- a/examples/s3/makebucketwithobjectlock.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// +build ignore
-
-/*
- * MinIO Go Library for Amazon S3 Compatible Cloud Storage
- * Copyright 2015-2019 MinIO, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package main
-
-import (
-	"context"
-	"log"
-
-	minio "github.com/minio/minio-go/v7"
-)
-
-func main() {
-	// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are
-	// dummy values, please replace them with original values.
-
-	// Requests are always secure (HTTPS) by default. Set secure=false to enable insecure (HTTP) access.
-	// This boolean value is the last argument for New().
-
-	// New returns an Amazon S3 compatible client object. API compatibility (v2 or v4) is automatically
-	// determined based on the Endpoint value.
-	s3Client, err := minio.New("s3.amazonaws.com", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
-	if err != nil {
-		log.Fatalln(err)
-	}
-
-	err = s3Client.MakeBucketWithObjectLock(context.Background(), "my-bucketname", "us-east-1")
-	if err != nil {
-		log.Fatalln(err)
-	}
-	log.Println("Success")
-}
diff --git a/go.sum b/go.sum
index 2138141..6452387 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,6 @@
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
-github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@@ -12,7 +10,6 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
 github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
-github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
 github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
 github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
@@ -25,14 +22,11 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLD
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
-github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
-github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
+github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -45,7 +39,6 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
diff --git a/pkg/s3utils/utils.go b/pkg/s3utils/utils.go
index aa96a68..65f9392 100644
--- a/pkg/s3utils/utils.go
+++ b/pkg/s3utils/utils.go
@@ -262,6 +262,9 @@ func TagDecode(ctag string) map[string]string {
 // addition to the percent encoding performed by urlEncodePath() used
 // here, it also percent encodes '/' (forward slash)
 func TagEncode(tags map[string]string) string {
+	if tags == nil {
+		return ""
+	}
 	values := url.Values{}
 	for k, v := range tags {
 		values[k] = []string{v}


Test patch:
diff --git a/api-compose-object_test.go b/api-compose-object_test.go
index ab54af0..fbf8f66 100644
--- a/api-compose-object_test.go
+++ b/api-compose-object_test.go
@@ -17,6 +17,7 @@
 package minio
 
 import (
+	"net/http"
 	"reflect"
 	"strings"
 	"testing"
@@ -58,17 +59,17 @@ func TestCalculateEvenSplits(t *testing.T) {
 	testCases := []struct {
 		// input size and source object
 		size int64
-		src  SourceInfo
+		src  CopySrcOptions
 
 		// output part-indexes
 		starts, ends []int64
 	}{
-		{0, SourceInfo{start: -1}, nil, nil},
-		{1, SourceInfo{start: -1}, []int64{0}, []int64{0}},
-		{1, SourceInfo{start: 0}, []int64{0}, []int64{0}},
+		{0, CopySrcOptions{Start: -1}, nil, nil},
+		{1, CopySrcOptions{Start: -1}, []int64{0}, []int64{0}},
+		{1, CopySrcOptions{Start: 0}, []int64{0}, []int64{0}},
 
-		{gb1, SourceInfo{start: -1}, []int64{0, 536870912}, []int64{536870911, 1073741823}},
-		{gb5, SourceInfo{start: -1},
+		{gb1, CopySrcOptions{Start: -1}, []int64{0, 536870912}, []int64{536870911, 1073741823}},
+		{gb5, CopySrcOptions{Start: -1},
 			[]int64{0, 536870912, 1073741824, 1610612736, 2147483648, 2684354560,
 				3221225472, 3758096384, 4294967296, 4831838208},
 			[]int64{536870911, 1073741823, 1610612735, 2147483647, 2684354559, 3221225471,
@@ -76,13 +77,13 @@ func TestCalculateEvenSplits(t *testing.T) {
 		},
 
 		// 2 part splits
-		{gb5p1, SourceInfo{start: -1},
+		{gb5p1, CopySrcOptions{Start: -1},
 			[]int64{0, 536870913, 1073741825, 1610612737, 2147483649, 2684354561,
 				3221225473, 3758096385, 4294967297, 4831838209},
 			[]int64{536870912, 1073741824, 1610612736, 2147483648, 2684354560, 3221225472,
 				3758096384, 4294967296, 4831838208, 5368709120},
 		},
-		{gb5p1, SourceInfo{start: -1},
+		{gb5p1, CopySrcOptions{Start: -1},
 			[]int64{0, 536870913, 1073741825, 1610612737, 2147483649, 2684354561,
 				3221225473, 3758096385, 4294967297, 4831838209},
 			[]int64{536870912, 1073741824, 1610612736, 2147483648, 2684354560, 3221225472,
@@ -90,7 +91,7 @@ func TestCalculateEvenSplits(t *testing.T) {
 		},
 
 		// 3 part splits
-		{gb10p1, SourceInfo{start: -1},
+		{gb10p1, CopySrcOptions{Start: -1},
 			[]int64{0, 536870913, 1073741825, 1610612737, 2147483649, 2684354561,
 				3221225473, 3758096385, 4294967297, 4831838209, 5368709121,
 				5905580033, 6442450945, 6979321857, 7516192769, 8053063681,
@@ -100,7 +101,7 @@ func TestCalculateEvenSplits(t *testing.T) {
 				5905580032, 6442450944, 6979321856, 7516192768, 8053063680,
 				8589934592, 9126805504, 9663676416, 10200547328, 10737418240},
 		},
-		{gb10p2, SourceInfo{start: -1},
+		{gb10p2, CopySrcOptions{Start: -1},
 			[]int64{0, 536870913, 1073741826, 1610612738, 2147483650, 2684354562,
 				3221225474, 3758096386, 4294967298, 4831838210, 5368709122,
 				5905580034, 6442450946, 6979321858, 7516192770, 8053063682,
@@ -120,8 +121,7 @@ func TestCalculateEvenSplits(t *testing.T) {
 	}
 }
 
-func TestGetUserMetaHeadersMap(t *testing.T) {
-
+func TestDestOptions(t *testing.T) {
 	userMetadata := map[string]string{
 		"test":                "test",
 		"x-amz-acl":           "public-read-write",
@@ -130,26 +130,27 @@ func TestGetUserMetaHeadersMap(t *testing.T) {
 		"x-amz-grant-write":   "test@exo.ch",
 	}
 
-	destInfo, _ := NewDestinationInfo("bucket", "object", DestInfoOptions{UserMeta: userMetadata})
-
-	r := destInfo.getUserMetaHeadersMap(true)
+	r := make(http.Header)
 
-	i := 0
+	dst := CopyDestOptions{
+		Bucket:          "bucket",
+		Object:          "object",
+		ReplaceMetadata: true,
+		UserMetadata:    userMetadata,
+	}
+	dst.Marshal(r)
 
-	if _, ok := r["x-amz-metadata-directive"]; !ok {
-		t.Errorf("Test %d - metadata directive was expected but is missing", i)
-		i++
+	if v := r.Get("x-amz-metadata-directive"); v != "REPLACE" {
+		t.Errorf("Test - metadata directive was expected but is missing")
 	}
 
 	for k := range r {
 		if strings.HasSuffix(k, "test") && !strings.HasPrefix(k, "x-amz-meta-") {
-			t.Errorf("Test %d - meta %q was expected as an x amz meta", i, k)
-			i++
+			t.Errorf("Test meta %q was expected as an x amz meta", k)
 		}
 
 		if !strings.HasSuffix(k, "test") && strings.HasPrefix(k, "x-amz-meta-") {
-			t.Errorf("Test %d - an amz/standard/storageClass Header was expected but got an x amz meta data", i)
-			i++
+			t.Errorf("Test an amz/standard/storageClass Header was expected but got an x amz meta data")
 		}
 	}
 }
diff --git a/functional_tests.go b/functional_tests.go
index e259146..d507e58 100644
--- a/functional_tests.go
+++ b/functional_tests.go
@@ -194,7 +194,7 @@ func cleanupVersionedBucket(bucketName string, c *minio.Client) error {
 			}
 		}
 	}
-	for objPartInfo := range c.ListIncompleteUploads(context.Background(), bucketName, "", true, doneCh) {
+	for objPartInfo := range c.ListIncompleteUploads(context.Background(), bucketName, "", true) {
 		if objPartInfo.Err != nil {
 			return objPartInfo.Err
 		}
@@ -986,7 +986,6 @@ func testGetObjectWithVersioning() {
 		}
 
 		if !bytes.Equal(tmpBuffer.Bytes(), buffers[i]) {
-			fmt.Println(len(tmpBuffer.Bytes()), len(buffers[i]))
 			logError(testName, function, args, startTime, "", "unexpected content of GetObject()", err)
 			return
 		}
@@ -1092,20 +1091,21 @@ func testCopyObjectWithVersioning() {
 	}
 
 	// Copy Source
-	src := minio.NewSourceInfo(bucketName, objectName, nil)
-	src.SetVersionID(infos[0].VersionID)
-	args["src"] = src
+	srcOpts := minio.CopySrcOptions{
+		Bucket:    bucketName,
+		Object:    objectName,
+		VersionID: infos[0].VersionID,
+	}
+	args["src"] = srcOpts
 
-	dst, err := minio.NewDestinationInfo(bucketName, objectName+"-copy", minio.DestInfoOptions{})
-	args["dst"] = dst
-	if err != nil {
-		logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-		return
+	dstOpts := minio.CopyDestOptions{
+		Bucket: bucketName,
+		Object: objectName + "-copy",
 	}
+	args["dst"] = dstOpts
 
 	// Perform the Copy
-	_, err = c.CopyObject(context.Background(), dst, src)
-	if err != nil {
+	if _, err = c.CopyObject(context.Background(), dstOpts, srcOpts); err != nil {
 		logError(testName, function, args, startTime, "", "CopyObject failed", err)
 		return
 	}
@@ -1223,22 +1223,24 @@ func testComposeObjectWithVersioning() {
 
 	// Source objects to concatenate. We also specify decryption
 	// key for each
-	src1 := minio.NewSourceInfo(bucketName, objectName, nil)
-	src1.SetVersionID(results[0].VersionID)
-	src2 := minio.NewSourceInfo(bucketName, objectName, nil)
-	src2.SetVersionID(results[1].VersionID)
+	src1 := minio.CopySrcOptions{
+		Bucket:    bucketName,
+		Object:    objectName,
+		VersionID: results[0].VersionID,
+	}
 
-	// Create slice of sources.
-	srcs := []minio.SourceInfo{src1, src2}
+	src2 := minio.CopySrcOptions{
+		Bucket:    bucketName,
+		Object:    objectName,
+		VersionID: results[1].VersionID,
+	}
 
-	// Create destination info
-	dst, err := minio.NewDestinationInfo(bucketName, objectName+"-copy", minio.DestInfoOptions{})
-	if err != nil {
-		logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-		return
+	dst := minio.CopyDestOptions{
+		Bucket: bucketName,
+		Object: objectName + "-copy",
 	}
 
-	_, err = c.ComposeObject(context.Background(), dst, srcs)
+	_, err = c.ComposeObject(context.Background(), dst, src1, src2)
 	if err != nil {
 		logError(testName, function, args, startTime, "", "ComposeObject failed", err)
 		return
@@ -1535,14 +1537,14 @@ func testObjectTaggingWithVersioning() {
 	tagsV1 := map[string]string{"key1": "val1"}
 	err = c.PutObjectTagging(context.Background(), bucketName, objectName, tagsV1, minio.PutObjectTaggingOptions{VersionID: versions[0].VersionID})
 	if err != nil {
-		logError(testName, function, args, startTime, "", "PutObjectTaggingWithOptions (1) failed", err)
+		logError(testName, function, args, startTime, "", "PutObjectTagging (1) failed", err)
 		return
 	}
 
 	tagsV2 := map[string]string{"key2": "val2"}
 	err = c.PutObjectTagging(context.Background(), bucketName, objectName, tagsV2, minio.PutObjectTaggingOptions{VersionID: versions[1].VersionID})
 	if err != nil {
-		logError(testName, function, args, startTime, "", "PutObjectTaggingWithOptions (2) failed", err)
+		logError(testName, function, args, startTime, "", "PutObjectTagging (2) failed", err)
 		return
 	}
 
@@ -1560,7 +1562,7 @@ func testObjectTaggingWithVersioning() {
 
 	gotTagsV1, err := c.GetObjectTagging(context.Background(), bucketName, objectName, minio.GetObjectTaggingOptions{VersionID: versions[0].VersionID})
 	if err != nil {
-		logError(testName, function, args, startTime, "", "GetObjectTaggingWithOptions failed", err)
+		logError(testName, function, args, startTime, "", "GetObjectTagging failed", err)
 		return
 	}
 
@@ -1571,7 +1573,7 @@ func testObjectTaggingWithVersioning() {
 
 	gotTagsV2, err := c.GetObjectTagging(context.Background(), bucketName, objectName, minio.GetObjectTaggingOptions{})
 	if err != nil {
-		logError(testName, function, args, startTime, "", "GetObjectTaggingWithContext failed", err)
+		logError(testName, function, args, startTime, "", "GetObjectTaggingContext failed", err)
 		return
 	}
 
@@ -1582,14 +1584,14 @@ func testObjectTaggingWithVersioning() {
 
 	err = c.RemoveObjectTagging(context.Background(), bucketName, objectName, minio.RemoveObjectTaggingOptions{VersionID: versions[0].VersionID})
 	if err != nil {
-		logError(testName, function, args, startTime, "", "PutObjectTaggingWithOptions (2) failed", err)
+		logError(testName, function, args, startTime, "", "PutObjectTagging (2) failed", err)
 		return
 	}
 
 	emptyTags, err := c.GetObjectTagging(context.Background(), bucketName, objectName,
 		minio.GetObjectTaggingOptions{VersionID: versions[0].VersionID})
 	if err != nil {
-		logError(testName, function, args, startTime, "", "GetObjectTaggingWithOptions failed", err)
+		logError(testName, function, args, startTime, "", "GetObjectTagging failed", err)
 		return
 	}
 
@@ -2089,7 +2091,7 @@ func testGetObjectClosedTwice() {
 }
 
 // Test RemoveObjects request where context cancels after timeout
-func testRemoveObjectsWithContext() {
+func testRemoveObjectsContext() {
 	// Initialize logging params.
 	startTime := time.Now()
 	testName := getFuncName()
@@ -2554,7 +2556,7 @@ func testFPutObject() {
 }
 
 // Tests FPutObject request when context cancels after timeout
-func testFPutObjectWithContext() {
+func testFPutObjectContext() {
 	// initialize logging params
 	startTime := time.Now()
 	testName := getFuncName()
@@ -2602,7 +2604,7 @@ func testFPutObjectWithContext() {
 	var fName = getMintDataDirFilePath("datafile-1-MB")
 	if fName == "" {
 		// Make a temp file with 1 MiB bytes of data.
-		file, err := ioutil.TempFile(os.TempDir(), "FPutObjectWithContextTest")
+		file, err := ioutil.TempFile(os.TempDir(), "FPutObjectContextTest")
 		if err != nil {
 			logError(testName, function, args, startTime, "", "TempFile creation failed", err)
 			return
@@ -2623,7 +2625,7 @@ func testFPutObjectWithContext() {
 	}
 
 	// Set base object name
-	objectName := bucketName + "FPutObjectWithContext"
+	objectName := bucketName + "FPutObjectContext"
 	args["objectName"] = objectName
 	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
 	args["ctx"] = ctx
@@ -2661,11 +2663,11 @@ func testFPutObjectWithContext() {
 }
 
 // Tests FPutObject request when context cancels after timeout
-func testFPutObjectWithContextV2() {
+func testFPutObjectContextV2() {
 	// initialize logging params
 	startTime := time.Now()
 	testName := getFuncName()
-	function := "FPutObjectWithContext(ctx, bucketName, objectName, fileName, opts)"
+	function := "FPutObjectContext(ctx, bucketName, objectName, fileName, opts)"
 	args := map[string]interface{}{
 		"bucketName": "",
 		"objectName": "",
@@ -2708,7 +2710,7 @@ func testFPutObjectWithContextV2() {
 	var fName = getMintDataDirFilePath("datafile-1-MB")
 	if fName == "" {
 		// Make a temp file with 1 MiB bytes of data.
-		file, err := ioutil.TempFile(os.TempDir(), "FPutObjectWithContextTest")
+		file, err := ioutil.TempFile(os.TempDir(), "FPutObjectContextTest")
 		if err != nil {
 			logError(testName, function, args, startTime, "", "Temp file creation failed", err)
 			return
@@ -2730,7 +2732,7 @@ func testFPutObjectWithContextV2() {
 	}
 
 	// Set base object name
-	objectName := bucketName + "FPutObjectWithContext"
+	objectName := bucketName + "FPutObjectContext"
 	args["objectName"] = objectName
 
 	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
@@ -2769,7 +2771,7 @@ func testFPutObjectWithContextV2() {
 }
 
 // Test validates putObject with context to see if request cancellation is honored.
-func testPutObjectWithContext() {
+func testPutObjectContext() {
 	// initialize logging params
 	startTime := time.Now()
 	testName := getFuncName()
@@ -3607,63 +3609,26 @@ func testCopyObject() {
 	}
 
 	// Copy Source
-	src := minio.NewSourceInfo(bucketName, objectName, nil)
-	args["src"] = src
-
-	// Set copy conditions.
-
-	// All invalid conditions first.
-	err = src.SetModifiedSinceCond(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC))
-	if err == nil {
-		logError(testName, function, args, startTime, "", "SetModifiedSinceCond did not fail for invalid conditions", err)
-		return
-	}
-	err = src.SetUnmodifiedSinceCond(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC))
-	if err == nil {
-		logError(testName, function, args, startTime, "", "SetUnmodifiedSinceCond did not fail for invalid conditions", err)
-		return
-	}
-	err = src.SetMatchETagCond("")
-	if err == nil {
-		logError(testName, function, args, startTime, "", "SetMatchETagCond did not fail for invalid conditions", err)
-		return
-	}
-	err = src.SetMatchETagExceptCond("")
-	if err == nil {
-		logError(testName, function, args, startTime, "", "SetMatchETagExceptCond did not fail for invalid conditions", err)
-		return
-	}
-
-	err = src.SetModifiedSinceCond(time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC))
-	if err != nil {
-		logError(testName, function, args, startTime, "", "SetModifiedSinceCond failed", err)
-		return
-	}
-	err = src.SetMatchETagCond(objInfo.ETag)
-	if err != nil {
-		logError(testName, function, args, startTime, "", "SetMatchETagCond failed", err)
-		return
+	src := minio.CopySrcOptions{
+		Bucket: bucketName,
+		Object: objectName,
+		// Set copy conditions.
+		MatchETag:          objInfo.ETag,
+		MatchModifiedSince: time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC),
 	}
+	args["src"] = src
 
-	dst, err := minio.NewDestinationInfo(bucketName+"-copy", objectName+"-copy", minio.DestInfoOptions{})
-	args["dst"] = dst
-	if err != nil {
-		logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-		return
+	dst := minio.CopyDestOptions{
+		Bucket: bucketName + "-copy",
+		Object: objectName + "-copy",
 	}
 
 	// Perform the Copy
-	ui, err := c.CopyObject(context.Background(), dst, src)
-	if err != nil {
+	if _, err = c.CopyObject(context.Background(), dst, src); err != nil {
 		logError(testName, function, args, startTime, "", "CopyObject failed", err)
 		return
 	}
 
-	if ui.Size != 0 {
-		logError(testName, function, args, startTime, "", "CopyObject returned unexpeced size", nil)
-		return
-	}
-
 	// Source object
 	r, err = c.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
 	if err != nil {
@@ -3677,6 +3642,7 @@ func testCopyObject() {
 		logError(testName, function, args, startTime, "", "GetObject failed", err)
 		return
 	}
+
 	// Check the various fields of source object against destination object.
 	objInfo, err = r.Stat()
 	if err != nil {
@@ -3698,16 +3664,11 @@ func testCopyObject() {
 	readerCopy.Close()
 
 	// CopyObject again but with wrong conditions
-	src = minio.NewSourceInfo(bucketName, objectName, nil)
-	err = src.SetUnmodifiedSinceCond(time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC))
-	if err != nil {
-		logError(testName, function, args, startTime, "", "SetUnmodifiedSinceCond failed", err)
-		return
-	}
-	err = src.SetMatchETagExceptCond(objInfo.ETag)
-	if err != nil {
-		logError(testName, function, args, startTime, "", "SetMatchETagExceptCond failed", err)
-		return
+	src = minio.CopySrcOptions{
+		Bucket:               bucketName,
+		Object:               objectName,
+		MatchUnmodifiedSince: time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC),
+		NoMatchETag:          objInfo.ETag,
 	}
 
 	// Perform the Copy which should fail
@@ -3717,21 +3678,21 @@ func testCopyObject() {
 		return
 	}
 
-	// Perform the Copy which should update only metadata.
-	src = minio.NewSourceInfo(bucketName, objectName, nil)
-	dst, err = minio.NewDestinationInfo(bucketName, objectName,
-		minio.DestInfoOptions{
-			UserMeta: map[string]string{
-				"Copy": "should be same",
-			},
+	src = minio.CopySrcOptions{
+		Bucket: bucketName,
+		Object: objectName,
+	}
+
+	dst = minio.CopyDestOptions{
+		Bucket:          bucketName,
+		Object:          objectName,
+		ReplaceMetadata: true,
+		UserMetadata: map[string]string{
+			"Copy": "should be same",
 		},
-	)
+	}
 	args["dst"] = dst
 	args["src"] = src
-	if err != nil {
-		logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-		return
-	}
 
 	_, err = c.CopyObject(context.Background(), dst, src)
 	if err != nil {
@@ -5340,7 +5301,7 @@ func testFunctional() {
 		"isRecursive": isRecursive,
 	}
 
-	for obj := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{ForceV1: true, Prefix: objectName, Recursive: true}) {
+	for obj := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{UseV1: true, Prefix: objectName, Recursive: true}) {
 		if obj.Key == objectName {
 			objFound = true
 			break
@@ -6735,50 +6696,20 @@ func testCopyObjectV2() {
 	r.Close()
 
 	// Copy Source
-	src := minio.NewSourceInfo(bucketName, objectName, nil)
+	src := minio.CopySrcOptions{
+		Bucket:             bucketName,
+		Object:             objectName,
+		MatchModifiedSince: time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC),
+		MatchETag:          objInfo.ETag,
+	}
 	args["source"] = src
 
 	// Set copy conditions.
-
-	// All invalid conditions first.
-	err = src.SetModifiedSinceCond(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC))
-	if err == nil {
-		logError(testName, function, args, startTime, "", "SetModifiedSinceCond did not fail for invalid conditions", err)
-		return
-	}
-	err = src.SetUnmodifiedSinceCond(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC))
-	if err == nil {
-		logError(testName, function, args, startTime, "", "SetUnmodifiedSinceCond did not fail for invalid conditions", err)
-		return
-	}
-	err = src.SetMatchETagCond("")
-	if err == nil {
-		logError(testName, function, args, startTime, "", "SetMatchETagCond did not fail for invalid conditions", err)
-		return
-	}
-	err = src.SetMatchETagExceptCond("")
-	if err == nil {
-		logError(testName, function, args, startTime, "", "SetMatchETagExceptCond did not fail for invalid conditions", err)
-		return
-	}
-
-	err = src.SetModifiedSinceCond(time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC))
-	if err != nil {
-		logError(testName, function, args, startTime, "", "SetModifiedSinceCond failed", err)
-		return
-	}
-	err = src.SetMatchETagCond(objInfo.ETag)
-	if err != nil {
-		logError(testName, function, args, startTime, "", "SetMatchETagCond failed", err)
-		return
+	dst := minio.CopyDestOptions{
+		Bucket: bucketName + "-copy",
+		Object: objectName + "-copy",
 	}
-
-	dst, err := minio.NewDestinationInfo(bucketName+"-copy", objectName+"-copy", minio.DestInfoOptions{})
 	args["destination"] = dst
-	if err != nil {
-		logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-		return
-	}
 
 	// Perform the Copy
 	_, err = c.CopyObject(context.Background(), dst, src)
@@ -6820,16 +6751,11 @@ func testCopyObjectV2() {
 	readerCopy.Close()
 
 	// CopyObject again but with wrong conditions
-	src = minio.NewSourceInfo(bucketName, objectName, nil)
-	err = src.SetUnmodifiedSinceCond(time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC))
-	if err != nil {
-		logError(testName, function, args, startTime, "", "SetUnmodifiedSinceCond failed", err)
-		return
-	}
-	err = src.SetMatchETagExceptCond(objInfo.ETag)
-	if err != nil {
-		logError(testName, function, args, startTime, "", "SetMatchETagExceptCond failed", err)
-		return
+	src = minio.CopySrcOptions{
+		Bucket:               bucketName,
+		Object:               objectName,
+		MatchUnmodifiedSince: time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC),
+		NoMatchETag:          objInfo.ETag,
 	}
 
 	// Perform the Copy which should fail
@@ -6871,19 +6797,18 @@ func testComposeObjectErrorCasesWrapper(c *minio.Client) {
 
 	// Test that more than 10K source objects cannot be
 	// concatenated.
-	srcArr := [10001]minio.SourceInfo{}
+	srcArr := [10001]minio.CopySrcOptions{}
 	srcSlice := srcArr[:]
-	dst, err := minio.NewDestinationInfo(bucketName, "object", minio.DestInfoOptions{})
-	if err != nil {
-		logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-		return
+	dst := minio.CopyDestOptions{
+		Bucket: bucketName,
+		Object: "object",
 	}
 
 	args["destination"] = dst
 	// Just explain about srcArr in args["sourceList"]
 	// to stop having 10,001 null headers logged
 	args["sourceList"] = "source array of 10,001 elements"
-	if _, err := c.ComposeObject(context.Background(), dst, srcSlice); err == nil {
+	if _, err := c.ComposeObject(context.Background(), dst, srcSlice...); err == nil {
 		logError(testName, function, args, startTime, "", "Expected error in ComposeObject", err)
 		return
 	} else if err.Error() != "There must be as least one and up to 10000 source objects." {
@@ -6903,14 +6828,16 @@ func testComposeObjectErrorCasesWrapper(c *minio.Client) {
 	}
 	// 2. Set invalid range spec on the object (going beyond
 	// object size)
-	badSrc := minio.NewSourceInfo(bucketName, "badObject", nil)
-	err = badSrc.SetRange(1, badSrcSize)
-	if err != nil {
-		logError(testName, function, args, startTime, "", "Setting NewSourceInfo failed", err)
-		return
+	badSrc := minio.CopySrcOptions{
+		Bucket:     bucketName,
+		Object:     "badObject",
+		MatchRange: true,
+		Start:      1,
+		End:        badSrcSize,
 	}
+
 	// 3. ComposeObject call should fail.
-	if _, err := c.ComposeObject(context.Background(), dst, []minio.SourceInfo{badSrc}); err == nil {
+	if _, err := c.ComposeObject(context.Background(), dst, badSrc); err == nil {
 		logError(testName, function, args, startTime, "", "ComposeObject expected to fail", err)
 		return
 	} else if !strings.Contains(err.Error(), "has invalid segment-to-copy") {
@@ -6979,26 +6906,26 @@ func testComposeMultipleSources(c *minio.Client) {
 	}
 
 	// We will append 10 copies of the object.
-	srcs := []minio.SourceInfo{}
+	srcs := []minio.CopySrcOptions{}
 	for i := 0; i < 10; i++ {
-		srcs = append(srcs, minio.NewSourceInfo(bucketName, "srcObject", nil))
+		srcs = append(srcs, minio.CopySrcOptions{
+			Bucket: bucketName,
+			Object: "srcObject",
+		})
 	}
+
 	// make the last part very small
-	err = srcs[9].SetRange(0, 0)
-	if err != nil {
-		logError(testName, function, args, startTime, "", "SetRange failed", err)
-		return
-	}
+	srcs[9].MatchRange = true
+
 	args["sourceList"] = srcs
 
-	dst, err := minio.NewDestinationInfo(bucketName, "dstObject", minio.DestInfoOptions{})
+	dst := minio.CopyDestOptions{
+		Bucket: bucketName,
+		Object: "dstObject",
+	}
 	args["destination"] = dst
 
-	if err != nil {
-		logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-		return
-	}
-	ui, err := c.ComposeObject(context.Background(), dst, srcs)
+	ui, err := c.ComposeObject(context.Background(), dst, srcs...)
 	if err != nil {
 		logError(testName, function, args, startTime, "", "ComposeObject failed", err)
 		return
@@ -7092,33 +7019,40 @@ func testEncryptedEmptyObject() {
 	}
 
 	// 2. Test CopyObject for an empty object
-	dstInfo, err := minio.NewDestinationInfo(bucketName, "new-object", minio.DestInfoOptions{Encryption: sse})
-	if err != nil {
-		args["objectName"] = "new-object"
-		function = "NewDestinationInfo(bucketName, objectName, sse, userMetadata)"
-		logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-		return
+	src := minio.CopySrcOptions{
+		Bucket:     bucketName,
+		Object:     "object",
+		Encryption: sse,
+	}
+
+	dst := minio.CopyDestOptions{
+		Bucket:     bucketName,
+		Object:     "new-object",
+		Encryption: sse,
 	}
-	srcInfo := minio.NewSourceInfo(bucketName, "object", sse)
-	if _, err = c.CopyObject(context.Background(), dstInfo, srcInfo); err != nil {
-		function = "CopyObject(dstInfo, srcInfo)"
+
+	if _, err = c.CopyObject(context.Background(), dst, src); err != nil {
+		function = "CopyObject(dst, src)"
 		logError(testName, function, map[string]interface{}{}, startTime, "", "CopyObject failed", err)
 		return
 	}
 
 	// 3. Test Key rotation
 	newSSE := encrypt.DefaultPBKDF([]byte("Don't Panic"), []byte(bucketName+"new-object"))
-	dstInfo, err = minio.NewDestinationInfo(bucketName, "new-object", minio.DestInfoOptions{Encryption: newSSE})
-	if err != nil {
-		args["objectName"] = "new-object"
-		function = "NewDestinationInfo(bucketName, objectName, encryptSSEC, userMetadata)"
-		logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-		return
+	src = minio.CopySrcOptions{
+		Bucket:     bucketName,
+		Object:     "new-object",
+		Encryption: sse,
 	}
 
-	srcInfo = minio.NewSourceInfo(bucketName, "new-object", sse)
-	if _, err = c.CopyObject(context.Background(), dstInfo, srcInfo); err != nil {
-		function = "CopyObject(dstInfo, srcInfo)"
+	dst = minio.CopyDestOptions{
+		Bucket:     bucketName,
+		Object:     "new-object",
+		Encryption: newSSE,
+	}
+
+	if _, err = c.CopyObject(context.Background(), dst, src); err != nil {
+		function = "CopyObject(dst, src)"
 		logError(testName, function, map[string]interface{}{}, startTime, "", "CopyObject with key rotation failed", err)
 		return
 	}
@@ -7181,12 +7115,17 @@ func testEncryptedCopyObjectWrapper(c *minio.Client, bucketName string, sseSrc,
 	}
 
 	// 2. copy object and change encryption key
-	src := minio.NewSourceInfo(bucketName, "srcObject", srcEncryption)
+	src := minio.CopySrcOptions{
+		Bucket:     bucketName,
+		Object:     "srcObject",
+		Encryption: srcEncryption,
+	}
 	args["source"] = src
-	dst, err := minio.NewDestinationInfo(bucketName, "dstObject", minio.DestInfoOptions{Encryption: sseDst})
-	if err != nil {
-		logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-		return
+
+	dst := minio.CopyDestOptions{
+		Bucket:     bucketName,
+		Object:     "dstObject",
+		Encryption: sseDst,
 	}
 	args["destination"] = dst
 
@@ -7227,10 +7166,10 @@ func testEncryptedCopyObjectWrapper(c *minio.Client, bucketName string, sseSrc,
 		newSSE = encrypt.NewSSE()
 	}
 	if newSSE != nil {
-		dst, err = minio.NewDestinationInfo(bucketName, "srcObject", minio.DestInfoOptions{Encryption: newSSE})
-		if err != nil {
-			logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-			return
+		dst = minio.CopyDestOptions{
+			Bucket:     bucketName,
+			Object:     "srcObject",
+			Encryption: newSSE,
 		}
 		args["destination"] = dst
 
@@ -7257,15 +7196,19 @@ func testEncryptedCopyObjectWrapper(c *minio.Client, bucketName string, sseSrc,
 			return
 		}
 		reader.Close()
+
 		// Test in-place decryption.
-		dst, err = minio.NewDestinationInfo(bucketName, "srcObject", minio.DestInfoOptions{})
-		if err != nil {
-			logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-			return
+		dst = minio.CopyDestOptions{
+			Bucket: bucketName,
+			Object: "srcObject",
 		}
 		args["destination"] = dst
 
-		src = minio.NewSourceInfo(bucketName, "srcObject", newSSE)
+		src = minio.CopySrcOptions{
+			Bucket:     bucketName,
+			Object:     "srcObject",
+			Encryption: newSSE,
+		}
 		args["source"] = src
 		_, err = c.CopyObject(context.Background(), dst, src)
 		if err != nil {
@@ -7614,12 +7557,16 @@ func testDecryptedCopyObject() {
 		return
 	}
 
-	src := minio.NewSourceInfo(bucketName, objectName, encrypt.SSECopy(encryption))
+	src := minio.CopySrcOptions{
+		Bucket:     bucketName,
+		Object:     objectName,
+		Encryption: encrypt.SSECopy(encryption),
+	}
 	args["source"] = src
-	dst, err := minio.NewDestinationInfo(bucketName, "decrypted-"+objectName, minio.DestInfoOptions{})
-	if err != nil {
-		logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-		return
+
+	dst := minio.CopyDestOptions{
+		Bucket: bucketName,
+		Object: "decrypted-" + objectName,
 	}
 	args["destination"] = dst
 
@@ -9267,9 +9214,7 @@ func testUserMetadataCopyingWrapper(c *minio.Client) {
 		h = make(http.Header)
 		for k, vs := range objInfo.Metadata {
 			if strings.HasPrefix(strings.ToLower(k), "x-amz-meta-") {
-				for _, v := range vs {
-					h.Add(k, v)
-				}
+				h.Add(k, vs[0])
 			}
 		}
 		return h
@@ -9294,12 +9239,17 @@ func testUserMetadataCopyingWrapper(c *minio.Client) {
 	}
 
 	// 2. create source
-	src := minio.NewSourceInfo(bucketName, "srcObject", nil)
+	src := minio.CopySrcOptions{
+		Bucket: bucketName,
+		Object: "srcObject",
+	}
+
 	// 2.1 create destination with metadata set
-	dst1, err := minio.NewDestinationInfo(bucketName, "dstObject-1", minio.DestInfoOptions{UserMeta: map[string]string{"notmyheader": "notmyvalue"}})
-	if err != nil {
-		logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-		return
+	dst1 := minio.CopyDestOptions{
+		Bucket:          bucketName,
+		Object:          "dstObject-1",
+		UserMetadata:    map[string]string{"notmyheader": "notmyvalue"},
+		ReplaceMetadata: true,
 	}
 
 	// 3. Check that copying to an object with metadata set resets
@@ -9320,12 +9270,10 @@ func testUserMetadataCopyingWrapper(c *minio.Client) {
 	}
 
 	// 4. create destination with no metadata set and same source
-	dst2, err := minio.NewDestinationInfo(bucketName, "dstObject-2", minio.DestInfoOptions{})
-	if err != nil {
-		logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-		return
+	dst2 := minio.CopyDestOptions{
+		Bucket: bucketName,
+		Object: "dstObject-2",
 	}
-	src = minio.NewSourceInfo(bucketName, "srcObject", nil)
 
 	// 5. Check that copying to an object with no metadata set,
 	// copies metadata.
@@ -9344,20 +9292,16 @@ func testUserMetadataCopyingWrapper(c *minio.Client) {
 	}
 
 	// 6. Compose a pair of sources.
-	srcs := []minio.SourceInfo{
-		minio.NewSourceInfo(bucketName, "srcObject", nil),
-		minio.NewSourceInfo(bucketName, "srcObject", nil),
-	}
-	dst3, err := minio.NewDestinationInfo(bucketName, "dstObject-3", minio.DestInfoOptions{})
-	if err != nil {
-		logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-		return
+	dst3 := minio.CopyDestOptions{
+		Bucket:          bucketName,
+		Object:          "dstObject-3",
+		ReplaceMetadata: true,
 	}
 
 	function = "ComposeObject(destination, sources)"
-	args["source"] = srcs
+	args["source"] = []minio.CopySrcOptions{src, src}
 	args["destination"] = dst3
-	_, err = c.ComposeObject(context.Background(), dst3, srcs)
+	_, err = c.ComposeObject(context.Background(), dst3, src, src)
 	if err != nil {
 		logError(testName, function, args, startTime, "", "ComposeObject failed", err)
 		return
@@ -9370,20 +9314,17 @@ func testUserMetadataCopyingWrapper(c *minio.Client) {
 	}
 
 	// 7. Compose a pair of sources with dest user metadata set.
-	srcs = []minio.SourceInfo{
-		minio.NewSourceInfo(bucketName, "srcObject", nil),
-		minio.NewSourceInfo(bucketName, "srcObject", nil),
-	}
-	dst4, err := minio.NewDestinationInfo(bucketName, "dstObject-4", minio.DestInfoOptions{UserMeta: map[string]string{"notmyheader": "notmyvalue"}})
-	if err != nil {
-		logError(testName, function, args, startTime, "", "NewDestinationInfo failed", err)
-		return
+	dst4 := minio.CopyDestOptions{
+		Bucket:          bucketName,
+		Object:          "dstObject-4",
+		UserMetadata:    map[string]string{"notmyheader": "notmyvalue"},
+		ReplaceMetadata: true,
 	}
 
 	function = "ComposeObject(destination, sources)"
-	args["source"] = srcs
+	args["source"] = []minio.CopySrcOptions{src, src}
 	args["destination"] = dst4
-	_, err = c.ComposeObject(context.Background(), dst4, srcs)
+	_, err = c.ComposeObject(context.Background(), dst4, src, src)
 	if err != nil {
 		logError(testName, function, args, startTime, "", "ComposeObject failed", err)
 		return
@@ -9628,8 +9569,14 @@ func testStorageClassMetadataCopyObject() {
 	}
 
 	// Make server side copy of object uploaded in previous step
-	src := minio.NewSourceInfo(bucketName, "srcObjectRRSClass", nil)
-	dst, err := minio.NewDestinationInfo(bucketName, "srcObjectRRSClassCopy", minio.DestInfoOptions{})
+	src := minio.CopySrcOptions{
+		Bucket: bucketName,
+		Object: "srcObjectRRSClass",
+	}
+	dst := minio.CopyDestOptions{
+		Bucket: bucketName,
+		Object: "srcObjectRRSClassCopy",
+	}
 	if _, err = c.CopyObject(context.Background(), dst, src); err != nil {
 		logError(testName, function, args, startTime, "", "CopyObject failed on RRS", err)
 	}
@@ -9655,8 +9602,14 @@ func testStorageClassMetadataCopyObject() {
 	}
 
 	// Make server side copy of object uploaded in previous step
-	src = minio.NewSourceInfo(bucketName, "srcObjectSSClass", nil)
-	dst, err = minio.NewDestinationInfo(bucketName, "srcObjectSSClassCopy", minio.DestInfoOptions{})
+	src = minio.CopySrcOptions{
+		Bucket: bucketName,
+		Object: "srcObjectSSClass",
+	}
+	dst = minio.CopyDestOptions{
+		Bucket: bucketName,
+		Object: "srcObjectSSClassCopy",
+	}
 	if _, err = c.CopyObject(context.Background(), dst, src); err != nil {
 		logError(testName, function, args, startTime, "", "CopyObject failed on SS", err)
 	}
@@ -10148,7 +10101,7 @@ func testFunctionalV2() {
 		"objectName":  objectName,
 		"isRecursive": isRecursive,
 	}
-	for obj := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{ForceV1: true, Prefix: objectName, Recursive: isRecursive}) {
+	for obj := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{UseV1: true, Prefix: objectName, Recursive: isRecursive}) {
 		if obj.Key == objectName {
 			objFound = true
 			break
@@ -10414,7 +10367,7 @@ func testFunctionalV2() {
 }
 
 // Test get object with GetObject with context
-func testGetObjectWithContext() {
+func testGetObjectContext() {
 	// initialize logging params
 	startTime := time.Now()
 	testName := getFuncName()
@@ -10521,7 +10474,7 @@ func testGetObjectWithContext() {
 }
 
 // Test get object with FGetObject with a user provided context
-func testFGetObjectWithContext() {
+func testFGetObjectContext() {
 	// initialize logging params
 	startTime := time.Now()
 	testName := getFuncName()
@@ -10613,7 +10566,7 @@ func testFGetObjectWithContext() {
 }
 
 // Test get object ACLs with GetObjectACL with custom provided context
-func testGetObjectACLWithContext() {
+func testGetObjectACLContext() {
 	// initialize logging params
 	startTime := time.Now()
 	testName := getFuncName()
@@ -10783,7 +10736,7 @@ func testGetObjectACLWithContext() {
 }
 
 // Test validates putObject with context to see if request cancellation is honored for V2.
-func testPutObjectWithContextV2() {
+func testPutObjectContextV2() {
 	// initialize logging params
 	startTime := time.Now()
 	testName := getFuncName()
@@ -10864,7 +10817,7 @@ func testPutObjectWithContextV2() {
 }
 
 // Test get object with GetObject with custom context
-func testGetObjectWithContextV2() {
+func testGetObjectContextV2() {
 	// initialize logging params
 	startTime := time.Now()
 	testName := getFuncName()
@@ -10969,7 +10922,7 @@ func testGetObjectWithContextV2() {
 }
 
 // Test get object with FGetObject with custom context
-func testFGetObjectWithContextV2() {
+func testFGetObjectContextV2() {
 	// initialize logging params
 	startTime := time.Now()
 	testName := getFuncName()
@@ -11152,7 +11105,7 @@ func testListObjects() {
 		}
 	}
 
-	testList(c.ListObjects, bucketName, minio.ListObjectsOptions{Recursive: true, ForceV1: true})
+	testList(c.ListObjects, bucketName, minio.ListObjectsOptions{Recursive: true, UseV1: true})
 	testList(c.ListObjects, bucketName, minio.ListObjectsOptions{Recursive: true})
 
 	// Delete all objects and buckets
@@ -11165,11 +11118,11 @@ func testListObjects() {
 }
 
 // Test deleting multiple objects with object retention set in Governance mode
-func testRemoveObjectsWithOptions() {
+func testRemoveObjects() {
 	// initialize logging params
 	startTime := time.Now()
 	testName := getFuncName()
-	function := "RemoveObjectsWithOptions(bucketName, objectsCh, opts)"
+	function := "RemoveObjects(bucketName, objectsCh, opts)"
 	args := map[string]interface{}{
 		"bucketName":   "",
 		"objectPrefix": "",
@@ -11236,7 +11189,7 @@ func testRemoveObjectsWithOptions() {
 	go func() {
 		defer close(objectsCh)
 		// List all objects from a bucket-name with a matching prefix.
-		for object := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{ForceV1: true, Recursive: true}) {
+		for object := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{UseV1: true, Recursive: true}) {
 			if object.Err != nil {
 				log.Fatalln(object.Err)
 			}
@@ -11259,7 +11212,7 @@ func testRemoveObjectsWithOptions() {
 	go func() {
 		defer close(objectsCh1)
 		// List all objects from a bucket-name with a matching prefix.
-		for object := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{ForceV1: true, Recursive: true}) {
+		for object := range c.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{UseV1: true, Recursive: true}) {
 			if object.Err != nil {
 				log.Fatalln(object.Err)
 			}
@@ -11324,10 +11277,10 @@ func main() {
 		testPutObject0ByteV2()
 		testPutObjectNoLengthV2()
 		testPutObjectsUnknownV2()
-		testGetObjectWithContextV2()
-		testFPutObjectWithContextV2()
-		testFGetObjectWithContextV2()
-		testPutObjectWithContextV2()
+		testGetObjectContextV2()
+		testFPutObjectContextV2()
+		testFGetObjectContextV2()
+		testPutObjectContextV2()
 		testMakeBucketError()
 		testMakeBucketRegions()
 		testPutObjectWithMetadata()
@@ -11350,17 +11303,17 @@ func main() {
 		testFunctional()
 		testGetObjectModified()
 		testPutObjectUploadSeekedObject()
-		testGetObjectWithContext()
-		testFPutObjectWithContext()
-		testFGetObjectWithContext()
-		testGetObjectACLWithContext()
-		testPutObjectWithContext()
+		testGetObjectContext()
+		testFPutObjectContext()
+		testFGetObjectContext()
+		testGetObjectACLContext()
+		testPutObjectContext()
 		testStorageClassMetadataPutObject()
 		testStorageClassInvalidMetadataPutObject()
 		testStorageClassMetadataCopyObject()
 		testPutObjectWithContentLanguage()
 		testListObjects()
-		testRemoveObjectsWithOptions()
+		testRemoveObjects()
 		testListObjectVersions()
 		testStatObjectWithVersioning()
 		testGetObjectWithVersioning()