mirror of
				https://github.com/minio/minio.git
				synced 2025-10-30 00:05:02 -04:00 
			
		
		
		
	Allow CopyObject() in S3 gateway to support metadata (#5000)
Fixes #4924
This commit is contained in:
		
							parent
							
								
									53f3d2fd65
								
							
						
					
					
						commit
						89d528a4ed
					
				| @ -55,11 +55,11 @@ func (l *s3Objects) AnonPutObject(bucket string, object string, size int64, data | |||||||
| 
 | 
 | ||||||
| // AnonGetObject - Get object anonymously | // AnonGetObject - Get object anonymously | ||||||
| func (l *s3Objects) AnonGetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error { | func (l *s3Objects) AnonGetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error { | ||||||
| 	r := minio.NewGetReqHeaders() | 	opts := minio.GetObjectOptions{} | ||||||
| 	if err := r.SetRange(startOffset, startOffset+length-1); err != nil { | 	if err := opts.SetRange(startOffset, startOffset+length-1); err != nil { | ||||||
| 		return s3ToObjectError(traceError(err), bucket, key) | 		return s3ToObjectError(traceError(err), bucket, key) | ||||||
| 	} | 	} | ||||||
| 	object, _, err := l.anonClient.GetObject(bucket, key, r) | 	object, _, err := l.anonClient.GetObject(bucket, key, opts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return s3ToObjectError(traceError(err), bucket, key) | 		return s3ToObjectError(traceError(err), bucket, key) | ||||||
| 	} | 	} | ||||||
| @ -75,8 +75,7 @@ func (l *s3Objects) AnonGetObject(bucket string, key string, startOffset int64, | |||||||
| 
 | 
 | ||||||
| // AnonGetObjectInfo - Get object info anonymously | // AnonGetObjectInfo - Get object info anonymously | ||||||
| func (l *s3Objects) AnonGetObjectInfo(bucket string, object string) (objInfo ObjectInfo, e error) { | func (l *s3Objects) AnonGetObjectInfo(bucket string, object string) (objInfo ObjectInfo, e error) { | ||||||
| 	r := minio.NewHeadReqHeaders() | 	oi, err := l.anonClient.StatObject(bucket, object, minio.StatObjectOptions{}) | ||||||
| 	oi, err := l.anonClient.StatObject(bucket, object, r) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return objInfo, s3ToObjectError(traceError(err), bucket, object) | 		return objInfo, s3ToObjectError(traceError(err), bucket, object) | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -19,7 +19,6 @@ package cmd | |||||||
| import ( | import ( | ||||||
| 	"io" | 	"io" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strings" |  | ||||||
| 
 | 
 | ||||||
| 	"encoding/hex" | 	"encoding/hex" | ||||||
| 
 | 
 | ||||||
| @ -290,22 +289,20 @@ func fromMinioClientListBucketResult(bucket string, result minio.ListBucketResul | |||||||
| // startOffset indicates the starting read location of the object. | // startOffset indicates the starting read location of the object. | ||||||
| // length indicates the total length of the object. | // length indicates the total length of the object. | ||||||
| func (l *s3Objects) GetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error { | func (l *s3Objects) GetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error { | ||||||
| 	r := minio.NewGetReqHeaders() |  | ||||||
| 
 |  | ||||||
| 	if length < 0 && length != -1 { | 	if length < 0 && length != -1 { | ||||||
| 		return s3ToObjectError(traceError(errInvalidArgument), bucket, key) | 		return s3ToObjectError(traceError(errInvalidArgument), bucket, key) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	opts := minio.GetObjectOptions{} | ||||||
| 	if startOffset >= 0 && length >= 0 { | 	if startOffset >= 0 && length >= 0 { | ||||||
| 		if err := r.SetRange(startOffset, startOffset+length-1); err != nil { | 		if err := opts.SetRange(startOffset, startOffset+length-1); err != nil { | ||||||
| 			return s3ToObjectError(traceError(err), bucket, key) | 			return s3ToObjectError(traceError(err), bucket, key) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	object, _, err := l.Client.GetObject(bucket, key, r) | 	object, _, err := l.Client.GetObject(bucket, key, opts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return s3ToObjectError(traceError(err), bucket, key) | 		return s3ToObjectError(traceError(err), bucket, key) | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	defer object.Close() | 	defer object.Close() | ||||||
| 
 | 
 | ||||||
| 	if _, err := io.Copy(writer, object); err != nil { | 	if _, err := io.Copy(writer, object); err != nil { | ||||||
| @ -333,8 +330,7 @@ func fromMinioClientObjectInfo(bucket string, oi minio.ObjectInfo) ObjectInfo { | |||||||
| 
 | 
 | ||||||
| // GetObjectInfo reads object info and replies back ObjectInfo | // GetObjectInfo reads object info and replies back ObjectInfo | ||||||
| func (l *s3Objects) GetObjectInfo(bucket string, object string) (objInfo ObjectInfo, err error) { | func (l *s3Objects) GetObjectInfo(bucket string, object string) (objInfo ObjectInfo, err error) { | ||||||
| 	r := minio.NewHeadReqHeaders() | 	oi, err := l.Client.StatObject(bucket, object, minio.StatObjectOptions{}) | ||||||
| 	oi, err := l.Client.StatObject(bucket, object, r) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return ObjectInfo{}, s3ToObjectError(traceError(err), bucket, object) | 		return ObjectInfo{}, s3ToObjectError(traceError(err), bucket, object) | ||||||
| 	} | 	} | ||||||
| @ -361,35 +357,17 @@ func (l *s3Objects) PutObject(bucket string, object string, data *HashReader, me | |||||||
| 	return fromMinioClientObjectInfo(bucket, oi), nil | 	return fromMinioClientObjectInfo(bucket, oi), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // CopyObject copies a blob from source container to destination container. | // CopyObject copies an object from source bucket to a destination bucket. | ||||||
| func (l *s3Objects) CopyObject(srcBucket string, srcObject string, dstBucket string, dstObject string, metadata map[string]string) (objInfo ObjectInfo, err error) { | func (l *s3Objects) CopyObject(srcBucket string, srcObject string, dstBucket string, dstObject string, metadata map[string]string) (objInfo ObjectInfo, err error) { | ||||||
| 	// Source object | 	// Set this header such that following CopyObject() always sets the right metadata on the destination. | ||||||
| 	src := minio.NewSourceInfo(srcBucket, srcObject, nil) | 	// metadata input is already a trickled down value from interpreting x-amz-metadata-directive at | ||||||
| 
 | 	// handler layer. So what we have right now is supposed to be applied on the destination object anyways. | ||||||
| 	// Destination object | 	// So preserve it by adding "REPLACE" directive to save all the metadata set by CopyObject API. | ||||||
| 	var xamzMeta = map[string]string{} | 	metadata["x-amz-metadata-directive"] = "REPLACE" | ||||||
| 	for key := range metadata { | 	if _, err = l.Client.CopyObject(srcBucket, srcObject, dstBucket, dstObject, metadata); err != nil { | ||||||
| 		for _, prefix := range userMetadataKeyPrefixes { |  | ||||||
| 			if strings.HasPrefix(key, prefix) { |  | ||||||
| 				xamzMeta[key] = metadata[key] |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	dst, err := minio.NewDestinationInfo(dstBucket, dstObject, nil, xamzMeta) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return objInfo, s3ToObjectError(traceError(err), dstBucket, dstObject) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err = l.Client.CopyObject(dst, src); err != nil { |  | ||||||
| 		return objInfo, s3ToObjectError(traceError(err), srcBucket, srcObject) | 		return objInfo, s3ToObjectError(traceError(err), srcBucket, srcObject) | ||||||
| 	} | 	} | ||||||
| 
 | 	return l.GetObjectInfo(dstBucket, dstObject) | ||||||
| 	oi, err := l.GetObjectInfo(dstBucket, dstObject) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return objInfo, s3ToObjectError(traceError(err), dstBucket, dstObject) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return oi, nil |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // DeleteObject deletes a blob in bucket | // DeleteObject deletes a blob in bucket | ||||||
|  | |||||||
| @ -398,7 +398,9 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		errorIf(err, "found invalid http request header") | 		errorIf(err, "found invalid http request header") | ||||||
| 		writeErrorResponse(w, ErrInternalError, r.URL) | 		writeErrorResponse(w, ErrInternalError, r.URL) | ||||||
|  | 		return | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	// Check if x-amz-metadata-directive was not set to REPLACE and source, | 	// Check if x-amz-metadata-directive was not set to REPLACE and source, | ||||||
| 	// desination are same objects. | 	// desination are same objects. | ||||||
| 	if !isMetadataReplace(r.Header) && cpSrcDstSame { | 	if !isMetadataReplace(r.Header) && cpSrcDstSame { | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								vendor/github.com/minio/minio-go/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/minio/minio-go/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -224,6 +224,7 @@ The full API Reference is available here. | |||||||
| ### Full Examples : Encrypted Object Operations | ### Full Examples : Encrypted Object Operations | ||||||
| * [put-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/put-encrypted-object.go) | * [put-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/put-encrypted-object.go) | ||||||
| * [get-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/get-encrypted-object.go) | * [get-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/get-encrypted-object.go) | ||||||
|  | * [fput-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/fputencrypted-object.go) | ||||||
| 
 | 
 | ||||||
| ### Full Examples : Presigned Operations | ### Full Examples : Presigned Operations | ||||||
| * [presignedgetobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedgetobject.go) | * [presignedgetobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedgetobject.go) | ||||||
|  | |||||||
							
								
								
									
										51
									
								
								vendor/github.com/minio/minio-go/api-compose-object.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										51
									
								
								vendor/github.com/minio/minio-go/api-compose-object.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -244,11 +244,11 @@ func (s *SourceInfo) getProps(c Client) (size int64, etag string, userMeta map[s | |||||||
| 	// Get object info - need size and etag here. Also, decryption | 	// Get object info - need size and etag here. Also, decryption | ||||||
| 	// headers are added to the stat request if given. | 	// headers are added to the stat request if given. | ||||||
| 	var objInfo ObjectInfo | 	var objInfo ObjectInfo | ||||||
| 	rh := NewGetReqHeaders() | 	opts := StatObjectOptions{} | ||||||
| 	for k, v := range s.decryptKey.getSSEHeaders(false) { | 	for k, v := range s.decryptKey.getSSEHeaders(false) { | ||||||
| 		rh.Set(k, v) | 		opts.Set(k, v) | ||||||
| 	} | 	} | ||||||
| 	objInfo, err = c.statObject(s.bucket, s.object, rh) | 	objInfo, err = c.statObject(s.bucket, s.object, opts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		err = fmt.Errorf("Could not stat object - %s/%s: %v", s.bucket, s.object, err) | 		err = fmt.Errorf("Could not stat object - %s/%s: %v", s.bucket, s.object, err) | ||||||
| 	} else { | 	} else { | ||||||
| @ -266,6 +266,51 @@ func (s *SourceInfo) getProps(c Client) (size int64, etag string, userMeta map[s | |||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Low level implementation of CopyObject API, supports only upto 5GiB worth of copy. | ||||||
|  | func (c Client) copyObjectDo(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, | ||||||
|  | 	metadata map[string]string) (ObjectInfo, error) { | ||||||
|  | 
 | ||||||
|  | 	// Build headers. | ||||||
|  | 	headers := make(http.Header) | ||||||
|  | 
 | ||||||
|  | 	// Set all the metadata headers. | ||||||
|  | 	for k, v := range metadata { | ||||||
|  | 		headers.Set(k, v) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Set the source header | ||||||
|  | 	headers.Set("x-amz-copy-source", s3utils.EncodePath(srcBucket+"/"+srcObject)) | ||||||
|  | 
 | ||||||
|  | 	// Send upload-part-copy request | ||||||
|  | 	resp, err := c.executeMethod(ctx, "PUT", requestMetadata{ | ||||||
|  | 		bucketName:   destBucket, | ||||||
|  | 		objectName:   destObject, | ||||||
|  | 		customHeader: headers, | ||||||
|  | 	}) | ||||||
|  | 	defer closeResponse(resp) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return ObjectInfo{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Check if we got an error response. | ||||||
|  | 	if resp.StatusCode != http.StatusOK { | ||||||
|  | 		return ObjectInfo{}, httpRespToErrorResponse(resp, srcBucket, srcObject) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	cpObjRes := copyObjectResult{} | ||||||
|  | 	err = xmlDecoder(resp.Body, &cpObjRes) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return ObjectInfo{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	objInfo := ObjectInfo{ | ||||||
|  | 		Key:          destObject, | ||||||
|  | 		ETag:         strings.Trim(cpObjRes.ETag, "\""), | ||||||
|  | 		LastModified: cpObjRes.LastModified, | ||||||
|  | 	} | ||||||
|  | 	return objInfo, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // uploadPartCopy - helper function to create a part in a multipart | // uploadPartCopy - helper function to create a part in a multipart | ||||||
| // upload via an upload-part-copy request | // upload via an upload-part-copy request | ||||||
| // https://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadUploadPartCopy.html | // https://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadUploadPartCopy.html | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								vendor/github.com/minio/minio-go/api-get-object-context.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/minio/minio-go/api-get-object-context.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -19,6 +19,7 @@ package minio | |||||||
| import "context" | import "context" | ||||||
| 
 | 
 | ||||||
| // GetObjectWithContext - returns an seekable, readable object. | // GetObjectWithContext - returns an seekable, readable object. | ||||||
| func (c Client) GetObjectWithContext(ctx context.Context, bucketName, objectName string) (*Object, error) { | // The options can be used to specify the GET request further. | ||||||
| 	return c.getObjectWithContext(ctx, bucketName, objectName) | func (c Client) GetObjectWithContext(ctx context.Context, bucketName, objectName string, opts GetObjectOptions) (*Object, error) { | ||||||
|  | 	return c.getObjectWithContext(ctx, bucketName, objectName, opts) | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										28
									
								
								vendor/github.com/minio/minio-go/api-get-object-file.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								vendor/github.com/minio/minio-go/api-get-object-file.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -21,23 +21,34 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/minio/minio-go/pkg/encrypt" | ||||||
|  | 
 | ||||||
| 	"context" | 	"context" | ||||||
| 
 | 
 | ||||||
| 	"github.com/minio/minio-go/pkg/s3utils" | 	"github.com/minio/minio-go/pkg/s3utils" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // FGetObjectWithContext - download contents of an object to a local file. | // FGetObjectWithContext - download contents of an object to a local file. | ||||||
| func (c Client) FGetObjectWithContext(ctx context.Context, bucketName, objectName, filePath string) error { | // The options can be used to specify the GET request further. | ||||||
| 	return c.fGetObjectWithContext(ctx, bucketName, objectName, filePath) | func (c Client) FGetObjectWithContext(ctx context.Context, bucketName, objectName, filePath string, opts GetObjectOptions) error { | ||||||
|  | 	return c.fGetObjectWithContext(ctx, bucketName, objectName, filePath, opts) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // FGetObject - download contents of an object to a local file. | // FGetObject - download contents of an object to a local file. | ||||||
| func (c Client) FGetObject(bucketName, objectName, filePath string) error { | func (c Client) FGetObject(bucketName, objectName, filePath string, opts GetObjectOptions) error { | ||||||
| 	return c.fGetObjectWithContext(context.Background(), bucketName, objectName, filePath) | 	return c.fGetObjectWithContext(context.Background(), bucketName, objectName, filePath, opts) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FGetEncryptedObject - Decrypt and store an object at filePath. | ||||||
|  | func (c Client) FGetEncryptedObject(bucketName, objectName, filePath string, materials encrypt.Materials) error { | ||||||
|  | 	if materials == nil { | ||||||
|  | 		return ErrInvalidArgument("Unable to recognize empty encryption properties") | ||||||
|  | 	} | ||||||
|  | 	return c.FGetObject(bucketName, objectName, filePath, GetObjectOptions{Materials: materials}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // fGetObjectWithContext - fgetObject wrapper function with context | // fGetObjectWithContext - fgetObject wrapper function with context | ||||||
| func (c Client) fGetObjectWithContext(ctx context.Context, bucketName, objectName, filePath string) error { | func (c Client) fGetObjectWithContext(ctx context.Context, bucketName, objectName, filePath string, opts GetObjectOptions) error { | ||||||
| 	// Input validation. | 	// Input validation. | ||||||
| 	if err := s3utils.CheckValidBucketName(bucketName); err != nil { | 	if err := s3utils.CheckValidBucketName(bucketName); err != nil { | ||||||
| 		return err | 		return err | ||||||
| @ -72,7 +83,7 @@ func (c Client) fGetObjectWithContext(ctx context.Context, bucketName, objectNam | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Gather md5sum. | 	// Gather md5sum. | ||||||
| 	objectStat, err := c.StatObject(bucketName, objectName) | 	objectStat, err := c.StatObject(bucketName, objectName, StatObjectOptions{opts}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @ -94,13 +105,12 @@ func (c Client) fGetObjectWithContext(ctx context.Context, bucketName, objectNam | |||||||
| 
 | 
 | ||||||
| 	// Initialize get object request headers to set the | 	// Initialize get object request headers to set the | ||||||
| 	// appropriate range offsets to read from. | 	// appropriate range offsets to read from. | ||||||
| 	reqHeaders := NewGetReqHeaders() |  | ||||||
| 	if st.Size() > 0 { | 	if st.Size() > 0 { | ||||||
| 		reqHeaders.SetRange(st.Size(), 0) | 		opts.SetRange(st.Size(), 0) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Seek to current position for incoming reader. | 	// Seek to current position for incoming reader. | ||||||
| 	objectReader, objectStat, err := c.getObject(ctx, bucketName, objectName, reqHeaders) | 	objectReader, objectStat, err := c.getObject(ctx, bucketName, objectName, opts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | |||||||
							
								
								
									
										88
									
								
								vendor/github.com/minio/minio-go/api-get-object.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										88
									
								
								vendor/github.com/minio/minio-go/api-get-object.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -37,32 +37,16 @@ func (c Client) GetEncryptedObject(bucketName, objectName string, encryptMateria | |||||||
| 		return nil, ErrInvalidArgument("Unable to recognize empty encryption properties") | 		return nil, ErrInvalidArgument("Unable to recognize empty encryption properties") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Fetch encrypted object | 	return c.GetObject(bucketName, objectName, GetObjectOptions{Materials: encryptMaterials}) | ||||||
| 	encReader, err := c.GetObject(bucketName, objectName) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	// Stat object to get its encryption metadata |  | ||||||
| 	st, err := encReader.Stat() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Setup object for decrytion, object is transparently |  | ||||||
| 	// decrypted as the consumer starts reading. |  | ||||||
| 	encryptMaterials.SetupDecryptMode(encReader, st.Metadata.Get(amzHeaderIV), st.Metadata.Get(amzHeaderKey)) |  | ||||||
| 
 |  | ||||||
| 	// Success. |  | ||||||
| 	return encryptMaterials, nil |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetObject - returns an seekable, readable object. | // GetObject - returns an seekable, readable object. | ||||||
| func (c Client) GetObject(bucketName, objectName string) (*Object, error) { | func (c Client) GetObject(bucketName, objectName string, opts GetObjectOptions) (*Object, error) { | ||||||
| 	return c.getObjectWithContext(context.Background(), bucketName, objectName) | 	return c.getObjectWithContext(context.Background(), bucketName, objectName, opts) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetObject wrapper function that accepts a request context | // GetObject wrapper function that accepts a request context | ||||||
| func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName string) (*Object, error) { | func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName string, opts GetObjectOptions) (*Object, error) { | ||||||
| 	// Input validation. | 	// Input validation. | ||||||
| 	if err := s3utils.CheckValidBucketName(bucketName); err != nil { | 	if err := s3utils.CheckValidBucketName(bucketName); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @ -108,34 +92,26 @@ func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName | |||||||
| 				if req.isFirstReq { | 				if req.isFirstReq { | ||||||
| 					// First request is a Read/ReadAt. | 					// First request is a Read/ReadAt. | ||||||
| 					if req.isReadOp { | 					if req.isReadOp { | ||||||
| 						reqHeaders := NewGetReqHeaders() |  | ||||||
| 						// Differentiate between wanting the whole object and just a range. | 						// Differentiate between wanting the whole object and just a range. | ||||||
| 						if req.isReadAt { | 						if req.isReadAt { | ||||||
| 							// If this is a ReadAt request only get the specified range. | 							// If this is a ReadAt request only get the specified range. | ||||||
| 							// Range is set with respect to the offset and length of the buffer requested. | 							// Range is set with respect to the offset and length of the buffer requested. | ||||||
| 							// Do not set objectInfo from the first readAt request because it will not get | 							// Do not set objectInfo from the first readAt request because it will not get | ||||||
| 							// the whole object. | 							// the whole object. | ||||||
| 							reqHeaders.SetRange(req.Offset, req.Offset+int64(len(req.Buffer))-1) | 							opts.SetRange(req.Offset, req.Offset+int64(len(req.Buffer))-1) | ||||||
| 							httpReader, objectInfo, err = c.getObject(ctx, bucketName, objectName, reqHeaders) | 						} else if req.Offset > 0 { | ||||||
| 						} else { | 							opts.SetRange(req.Offset, 0) | ||||||
| 							if req.Offset > 0 { |  | ||||||
| 								reqHeaders.SetRange(req.Offset, 0) |  | ||||||
| 							} |  | ||||||
| 
 |  | ||||||
| 							// First request is a Read request. |  | ||||||
| 							httpReader, objectInfo, err = c.getObject(ctx, bucketName, objectName, reqHeaders) |  | ||||||
| 						} | 						} | ||||||
|  | 						httpReader, objectInfo, err = c.getObject(ctx, bucketName, objectName, opts) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
| 							resCh <- getResponse{ | 							resCh <- getResponse{Error: err} | ||||||
| 								Error: err, |  | ||||||
| 							} |  | ||||||
| 							return | 							return | ||||||
| 						} | 						} | ||||||
| 						etag = objectInfo.ETag | 						etag = objectInfo.ETag | ||||||
| 						// Read at least firstReq.Buffer bytes, if not we have | 						// Read at least firstReq.Buffer bytes, if not we have | ||||||
| 						// reached our EOF. | 						// reached our EOF. | ||||||
| 						size, err := io.ReadFull(httpReader, req.Buffer) | 						size, err := io.ReadFull(httpReader, req.Buffer) | ||||||
| 						if err == io.ErrUnexpectedEOF { | 						if size > 0 && err == io.ErrUnexpectedEOF { | ||||||
| 							// If an EOF happens after reading some but not | 							// If an EOF happens after reading some but not | ||||||
| 							// all the bytes ReadFull returns ErrUnexpectedEOF | 							// all the bytes ReadFull returns ErrUnexpectedEOF | ||||||
| 							err = io.EOF | 							err = io.EOF | ||||||
| @ -150,7 +126,7 @@ func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName | |||||||
| 					} else { | 					} else { | ||||||
| 						// First request is a Stat or Seek call. | 						// First request is a Stat or Seek call. | ||||||
| 						// Only need to run a StatObject until an actual Read or ReadAt request comes through. | 						// Only need to run a StatObject until an actual Read or ReadAt request comes through. | ||||||
| 						objectInfo, err = c.StatObject(bucketName, objectName) | 						objectInfo, err = c.statObject(bucketName, objectName, StatObjectOptions{opts}) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
| 							resCh <- getResponse{ | 							resCh <- getResponse{ | ||||||
| 								Error: err, | 								Error: err, | ||||||
| @ -165,11 +141,10 @@ func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName | |||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} else if req.settingObjectInfo { // Request is just to get objectInfo. | 				} else if req.settingObjectInfo { // Request is just to get objectInfo. | ||||||
| 					reqHeaders := NewGetReqHeaders() |  | ||||||
| 					if etag != "" { | 					if etag != "" { | ||||||
| 						reqHeaders.SetMatchETag(etag) | 						opts.SetMatchETag(etag) | ||||||
| 					} | 					} | ||||||
| 					objectInfo, err := c.statObject(bucketName, objectName, reqHeaders) | 					objectInfo, err := c.statObject(bucketName, objectName, StatObjectOptions{opts}) | ||||||
| 					if err != nil { | 					if err != nil { | ||||||
| 						resCh <- getResponse{ | 						resCh <- getResponse{ | ||||||
| 							Error: err, | 							Error: err, | ||||||
| @ -189,9 +164,8 @@ func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName | |||||||
| 					// new ones when they haven't been already. | 					// new ones when they haven't been already. | ||||||
| 					// All readAt requests are new requests. | 					// All readAt requests are new requests. | ||||||
| 					if req.DidOffsetChange || !req.beenRead { | 					if req.DidOffsetChange || !req.beenRead { | ||||||
| 						reqHeaders := NewGetReqHeaders() |  | ||||||
| 						if etag != "" { | 						if etag != "" { | ||||||
| 							reqHeaders.SetMatchETag(etag) | 							opts.SetMatchETag(etag) | ||||||
| 						} | 						} | ||||||
| 						if httpReader != nil { | 						if httpReader != nil { | ||||||
| 							// Close previously opened http reader. | 							// Close previously opened http reader. | ||||||
| @ -200,16 +174,11 @@ func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName | |||||||
| 						// If this request is a readAt only get the specified range. | 						// If this request is a readAt only get the specified range. | ||||||
| 						if req.isReadAt { | 						if req.isReadAt { | ||||||
| 							// Range is set with respect to the offset and length of the buffer requested. | 							// Range is set with respect to the offset and length of the buffer requested. | ||||||
| 							reqHeaders.SetRange(req.Offset, req.Offset+int64(len(req.Buffer))-1) | 							opts.SetRange(req.Offset, req.Offset+int64(len(req.Buffer))-1) | ||||||
| 							httpReader, _, err = c.getObject(ctx, bucketName, objectName, reqHeaders) | 						} else if req.Offset > 0 { // Range is set with respect to the offset. | ||||||
| 						} else { | 							opts.SetRange(req.Offset, 0) | ||||||
| 							// Range is set with respect to the offset. |  | ||||||
| 							if req.Offset > 0 { |  | ||||||
| 								reqHeaders.SetRange(req.Offset, 0) |  | ||||||
| 							} |  | ||||||
| 
 |  | ||||||
| 							httpReader, objectInfo, err = c.getObject(ctx, bucketName, objectName, reqHeaders) |  | ||||||
| 						} | 						} | ||||||
|  | 						httpReader, objectInfo, err = c.getObject(ctx, bucketName, objectName, opts) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
| 							resCh <- getResponse{ | 							resCh <- getResponse{ | ||||||
| 								Error: err, | 								Error: err, | ||||||
| @ -632,7 +601,7 @@ func newObject(reqCh chan<- getRequest, resCh <-chan getResponse, doneCh chan<- | |||||||
| // | // | ||||||
| // For more information about the HTTP Range header. | // For more information about the HTTP Range header. | ||||||
| // go to http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35. | // go to http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35. | ||||||
| func (c Client) getObject(ctx context.Context, bucketName, objectName string, reqHeaders RequestHeaders) (io.ReadCloser, ObjectInfo, error) { | func (c Client) getObject(ctx context.Context, bucketName, objectName string, opts GetObjectOptions) (io.ReadCloser, ObjectInfo, error) { | ||||||
| 	// Validate input arguments. | 	// Validate input arguments. | ||||||
| 	if err := s3utils.CheckValidBucketName(bucketName); err != nil { | 	if err := s3utils.CheckValidBucketName(bucketName); err != nil { | ||||||
| 		return nil, ObjectInfo{}, err | 		return nil, ObjectInfo{}, err | ||||||
| @ -641,17 +610,11 @@ func (c Client) getObject(ctx context.Context, bucketName, objectName string, re | |||||||
| 		return nil, ObjectInfo{}, err | 		return nil, ObjectInfo{}, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Set all the necessary reqHeaders. |  | ||||||
| 	customHeader := make(http.Header) |  | ||||||
| 	for key, value := range reqHeaders.Header { |  | ||||||
| 		customHeader[key] = value |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Execute GET on objectName. | 	// Execute GET on objectName. | ||||||
| 	resp, err := c.executeMethod(ctx, "GET", requestMetadata{ | 	resp, err := c.executeMethod(ctx, "GET", requestMetadata{ | ||||||
| 		bucketName:         bucketName, | 		bucketName:         bucketName, | ||||||
| 		objectName:         objectName, | 		objectName:         objectName, | ||||||
| 		customHeader:       customHeader, | 		customHeader:       opts.Header(), | ||||||
| 		contentSHA256Bytes: emptySHA256, | 		contentSHA256Bytes: emptySHA256, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -698,6 +661,15 @@ func (c Client) getObject(ctx context.Context, bucketName, objectName string, re | |||||||
| 		Metadata: extractObjMetadata(resp.Header), | 		Metadata: extractObjMetadata(resp.Header), | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	reader := resp.Body | ||||||
|  | 	if opts.Materials != nil { | ||||||
|  | 		err = opts.Materials.SetupDecryptMode(reader, objectStat.Metadata.Get(amzHeaderIV), objectStat.Metadata.Get(amzHeaderKey)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, ObjectInfo{}, err | ||||||
|  | 		} | ||||||
|  | 		reader = opts.Materials | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// do not close body here, caller will close | 	// do not close body here, caller will close | ||||||
| 	return resp.Body, objectStat, nil | 	return reader, objectStat, nil | ||||||
| } | } | ||||||
|  | |||||||
| @ -20,80 +20,94 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"time" | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/minio/minio-go/pkg/encrypt" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // RequestHeaders - implement methods for setting special | // GetObjectOptions are used to specify additional headers or options | ||||||
| // request headers for GET, HEAD object operations. | // during GET requests. | ||||||
| // http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html | type GetObjectOptions struct { | ||||||
| type RequestHeaders struct { | 	headers map[string]string | ||||||
| 	http.Header | 
 | ||||||
|  | 	Materials encrypt.Materials | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewGetReqHeaders - initializes a new request headers for GET request. | // StatObjectOptions are used to specify additional headers or options | ||||||
| func NewGetReqHeaders() RequestHeaders { | // during GET info/stat requests. | ||||||
| 	return RequestHeaders{ | type StatObjectOptions struct { | ||||||
| 		Header: make(http.Header), | 	GetObjectOptions | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewHeadReqHeaders - initializes a new request headers for HEAD request. | // Header returns the http.Header representation of the GET options. | ||||||
| func NewHeadReqHeaders() RequestHeaders { | func (o GetObjectOptions) Header() http.Header { | ||||||
| 	return RequestHeaders{ | 	headers := make(http.Header, len(o.headers)) | ||||||
| 		Header: make(http.Header), | 	for k, v := range o.headers { | ||||||
|  | 		headers.Set(k, v) | ||||||
| 	} | 	} | ||||||
|  | 	return headers | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Set adds a key value pair to the options. The | ||||||
|  | // key-value pair will be part of the HTTP GET request | ||||||
|  | // headers. | ||||||
|  | func (o *GetObjectOptions) Set(key, value string) { | ||||||
|  | 	if o.headers == nil { | ||||||
|  | 		o.headers = make(map[string]string) | ||||||
|  | 	} | ||||||
|  | 	o.headers[http.CanonicalHeaderKey(key)] = value | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetMatchETag - set match etag. | // SetMatchETag - set match etag. | ||||||
| func (c RequestHeaders) SetMatchETag(etag string) error { | func (o *GetObjectOptions) SetMatchETag(etag string) error { | ||||||
| 	if etag == "" { | 	if etag == "" { | ||||||
| 		return ErrInvalidArgument("ETag cannot be empty.") | 		return ErrInvalidArgument("ETag cannot be empty.") | ||||||
| 	} | 	} | ||||||
| 	c.Set("If-Match", "\""+etag+"\"") | 	o.Set("If-Match", "\""+etag+"\"") | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetMatchETagExcept - set match etag except. | // SetMatchETagExcept - set match etag except. | ||||||
| func (c RequestHeaders) SetMatchETagExcept(etag string) error { | func (o *GetObjectOptions) SetMatchETagExcept(etag string) error { | ||||||
| 	if etag == "" { | 	if etag == "" { | ||||||
| 		return ErrInvalidArgument("ETag cannot be empty.") | 		return ErrInvalidArgument("ETag cannot be empty.") | ||||||
| 	} | 	} | ||||||
| 	c.Set("If-None-Match", "\""+etag+"\"") | 	o.Set("If-None-Match", "\""+etag+"\"") | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetUnmodified - set unmodified time since. | // SetUnmodified - set unmodified time since. | ||||||
| func (c RequestHeaders) SetUnmodified(modTime time.Time) error { | func (o *GetObjectOptions) SetUnmodified(modTime time.Time) error { | ||||||
| 	if modTime.IsZero() { | 	if modTime.IsZero() { | ||||||
| 		return ErrInvalidArgument("Modified since cannot be empty.") | 		return ErrInvalidArgument("Modified since cannot be empty.") | ||||||
| 	} | 	} | ||||||
| 	c.Set("If-Unmodified-Since", modTime.Format(http.TimeFormat)) | 	o.Set("If-Unmodified-Since", modTime.Format(http.TimeFormat)) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetModified - set modified time since. | // SetModified - set modified time since. | ||||||
| func (c RequestHeaders) SetModified(modTime time.Time) error { | func (o *GetObjectOptions) SetModified(modTime time.Time) error { | ||||||
| 	if modTime.IsZero() { | 	if modTime.IsZero() { | ||||||
| 		return ErrInvalidArgument("Modified since cannot be empty.") | 		return ErrInvalidArgument("Modified since cannot be empty.") | ||||||
| 	} | 	} | ||||||
| 	c.Set("If-Modified-Since", modTime.Format(http.TimeFormat)) | 	o.Set("If-Modified-Since", modTime.Format(http.TimeFormat)) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetRange - set the start and end offset of the object to be read. | // SetRange - set the start and end offset of the object to be read. | ||||||
| // See https://tools.ietf.org/html/rfc7233#section-3.1 for reference. | // See https://tools.ietf.org/html/rfc7233#section-3.1 for reference. | ||||||
| func (c RequestHeaders) SetRange(start, end int64) error { | func (o *GetObjectOptions) SetRange(start, end int64) error { | ||||||
| 	switch { | 	switch { | ||||||
| 	case start == 0 && end < 0: | 	case start == 0 && end < 0: | ||||||
| 		// Read last '-end' bytes. `bytes=-N`. | 		// Read last '-end' bytes. `bytes=-N`. | ||||||
| 		c.Set("Range", fmt.Sprintf("bytes=%d", end)) | 		o.Set("Range", fmt.Sprintf("bytes=%d", end)) | ||||||
| 	case 0 < start && end == 0: | 	case 0 < start && end == 0: | ||||||
| 		// Read everything starting from offset | 		// Read everything starting from offset | ||||||
| 		// 'start'. `bytes=N-`. | 		// 'start'. `bytes=N-`. | ||||||
| 		c.Set("Range", fmt.Sprintf("bytes=%d-", start)) | 		o.Set("Range", fmt.Sprintf("bytes=%d-", start)) | ||||||
| 	case 0 <= start && start <= end: | 	case 0 <= start && start <= end: | ||||||
| 		// Read everything starting at 'start' till the | 		// Read everything starting at 'start' till the | ||||||
| 		// 'end'. `bytes=N-M` | 		// 'end'. `bytes=N-M` | ||||||
| 		c.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end)) | 		o.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end)) | ||||||
| 	default: | 	default: | ||||||
| 		// All other cases such as | 		// All other cases such as | ||||||
| 		// bytes=-3- | 		// bytes=-3- | ||||||
							
								
								
									
										2
									
								
								vendor/github.com/minio/minio-go/api-s3-datatypes.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/minio/minio-go/api-s3-datatypes.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -128,7 +128,7 @@ type initiator struct { | |||||||
| // copyObjectResult container for copy object response. | // copyObjectResult container for copy object response. | ||||||
| type copyObjectResult struct { | type copyObjectResult struct { | ||||||
| 	ETag         string | 	ETag         string | ||||||
| 	LastModified string // time string format "2006-01-02T15:04:05.000Z" | 	LastModified time.Time // time string format "2006-01-02T15:04:05.000Z" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ObjectPart container for particular part of an object. | // ObjectPart container for particular part of an object. | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								vendor/github.com/minio/minio-go/api-stat.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								vendor/github.com/minio/minio-go/api-stat.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -81,7 +81,7 @@ func extractObjMetadata(header http.Header) http.Header { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // StatObject verifies if object exists and you have permission to access. | // StatObject verifies if object exists and you have permission to access. | ||||||
| func (c Client) StatObject(bucketName, objectName string) (ObjectInfo, error) { | func (c Client) StatObject(bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) { | ||||||
| 	// Input validation. | 	// Input validation. | ||||||
| 	if err := s3utils.CheckValidBucketName(bucketName); err != nil { | 	if err := s3utils.CheckValidBucketName(bucketName); err != nil { | ||||||
| 		return ObjectInfo{}, err | 		return ObjectInfo{}, err | ||||||
| @ -89,12 +89,11 @@ func (c Client) StatObject(bucketName, objectName string) (ObjectInfo, error) { | |||||||
| 	if err := s3utils.CheckValidObjectName(objectName); err != nil { | 	if err := s3utils.CheckValidObjectName(objectName); err != nil { | ||||||
| 		return ObjectInfo{}, err | 		return ObjectInfo{}, err | ||||||
| 	} | 	} | ||||||
| 	reqHeaders := NewHeadReqHeaders() | 	return c.statObject(bucketName, objectName, opts) | ||||||
| 	return c.statObject(bucketName, objectName, reqHeaders) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Lower level API for statObject supporting pre-conditions and range headers. | // Lower level API for statObject supporting pre-conditions and range headers. | ||||||
| func (c Client) statObject(bucketName, objectName string, reqHeaders RequestHeaders) (ObjectInfo, error) { | func (c Client) statObject(bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) { | ||||||
| 	// Input validation. | 	// Input validation. | ||||||
| 	if err := s3utils.CheckValidBucketName(bucketName); err != nil { | 	if err := s3utils.CheckValidBucketName(bucketName); err != nil { | ||||||
| 		return ObjectInfo{}, err | 		return ObjectInfo{}, err | ||||||
| @ -103,17 +102,12 @@ func (c Client) statObject(bucketName, objectName string, reqHeaders RequestHead | |||||||
| 		return ObjectInfo{}, err | 		return ObjectInfo{}, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	customHeader := make(http.Header) |  | ||||||
| 	for k, v := range reqHeaders.Header { |  | ||||||
| 		customHeader[k] = v |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Execute HEAD on objectName. | 	// Execute HEAD on objectName. | ||||||
| 	resp, err := c.executeMethod(context.Background(), "HEAD", requestMetadata{ | 	resp, err := c.executeMethod(context.Background(), "HEAD", requestMetadata{ | ||||||
| 		bucketName:         bucketName, | 		bucketName:         bucketName, | ||||||
| 		objectName:         objectName, | 		objectName:         objectName, | ||||||
| 		contentSHA256Bytes: emptySHA256, | 		contentSHA256Bytes: emptySHA256, | ||||||
| 		customHeader:       customHeader, | 		customHeader:       opts.Header(), | ||||||
| 	}) | 	}) | ||||||
| 	defer closeResponse(resp) | 	defer closeResponse(resp) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | |||||||
							
								
								
									
										32
									
								
								vendor/github.com/minio/minio-go/core.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										32
									
								
								vendor/github.com/minio/minio-go/core.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -19,6 +19,7 @@ package minio | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/minio/minio-go/pkg/policy" | 	"github.com/minio/minio-go/pkg/policy" | ||||||
| ) | ) | ||||||
| @ -53,9 +54,30 @@ func (c Core) ListObjectsV2(bucketName, objectPrefix, continuationToken string, | |||||||
| 	return c.listObjectsV2Query(bucketName, objectPrefix, continuationToken, fetchOwner, delimiter, maxkeys) | 	return c.listObjectsV2Query(bucketName, objectPrefix, continuationToken, fetchOwner, delimiter, maxkeys) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // CopyObject - copies an object from source object to destination object on server side. | ||||||
|  | func (c Core) CopyObject(sourceBucket, sourceObject, destBucket, destObject string, metadata map[string]string) (ObjectInfo, error) { | ||||||
|  | 	return c.copyObjectDo(context.Background(), sourceBucket, sourceObject, destBucket, destObject, metadata) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // PutObject - Upload object. Uploads using single PUT call. | // PutObject - Upload object. Uploads using single PUT call. | ||||||
| func (c Core) PutObject(bucket, object string, data io.Reader, size int64, md5Sum, sha256Sum []byte, metadata map[string]string) (ObjectInfo, error) { | func (c Core) PutObject(bucket, object string, data io.Reader, size int64, md5Sum, sha256Sum []byte, metadata map[string]string) (ObjectInfo, error) { | ||||||
| 	return c.putObjectDo(context.Background(), bucket, object, data, md5Sum, sha256Sum, size, PutObjectOptions{UserMetadata: metadata}) | 	opts := PutObjectOptions{} | ||||||
|  | 	m := make(map[string]string) | ||||||
|  | 	for k, v := range metadata { | ||||||
|  | 		if strings.ToLower(k) == "content-encoding" { | ||||||
|  | 			opts.ContentEncoding = v | ||||||
|  | 		} else if strings.ToLower(k) == "content-disposition" { | ||||||
|  | 			opts.ContentDisposition = v | ||||||
|  | 		} else if strings.ToLower(k) == "content-type" { | ||||||
|  | 			opts.ContentType = v | ||||||
|  | 		} else if strings.ToLower(k) == "cache-control" { | ||||||
|  | 			opts.CacheControl = v | ||||||
|  | 		} else { | ||||||
|  | 			m[k] = metadata[k] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	opts.UserMetadata = m | ||||||
|  | 	return c.putObjectDo(context.Background(), bucket, object, data, md5Sum, sha256Sum, size, opts) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewMultipartUpload - Initiates new multipart upload and returns the new uploadID. | // NewMultipartUpload - Initiates new multipart upload and returns the new uploadID. | ||||||
| @ -111,12 +133,12 @@ func (c Core) PutBucketPolicy(bucket string, bucketPolicy policy.BucketAccessPol | |||||||
| // GetObject is a lower level API implemented to support reading | // GetObject is a lower level API implemented to support reading | ||||||
| // partial objects and also downloading objects with special conditions | // partial objects and also downloading objects with special conditions | ||||||
| // matching etag, modtime etc. | // matching etag, modtime etc. | ||||||
| func (c Core) GetObject(bucketName, objectName string, reqHeaders RequestHeaders) (io.ReadCloser, ObjectInfo, error) { | func (c Core) GetObject(bucketName, objectName string, opts GetObjectOptions) (io.ReadCloser, ObjectInfo, error) { | ||||||
| 	return c.getObject(context.Background(), bucketName, objectName, reqHeaders) | 	return c.getObject(context.Background(), bucketName, objectName, opts) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // StatObject is a lower level API implemented to support special | // StatObject is a lower level API implemented to support special | ||||||
| // conditions matching etag, modtime on a request. | // conditions matching etag, modtime on a request. | ||||||
| func (c Core) StatObject(bucketName, objectName string, reqHeaders RequestHeaders) (ObjectInfo, error) { | func (c Core) StatObject(bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) { | ||||||
| 	return c.statObject(bucketName, objectName, reqHeaders) | 	return c.statObject(bucketName, objectName, opts) | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										5889
									
								
								vendor/github.com/minio/minio-go/functional_tests.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5889
									
								
								vendor/github.com/minio/minio-go/functional_tests.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							| @ -336,10 +336,10 @@ | |||||||
| 			"revisionTime": "2016-02-29T08:42:30-08:00" | 			"revisionTime": "2016-02-29T08:42:30-08:00" | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "mqxOM3CsubB09O0nDEe4efu0JLQ=", | 			"checksumSHA1": "EkdIh5Mk2bRiARtdoqUfnBuyndk=", | ||||||
| 			"path": "github.com/minio/minio-go", | 			"path": "github.com/minio/minio-go", | ||||||
| 			"revision": "414c6b6a2e97428776cd831d9745589ebcf873e5", | 			"revision": "9690dc6c40e6ef271727848c04f974e801212ac1", | ||||||
| 			"revisionTime": "2017-09-27T19:03:45Z" | 			"revisionTime": "2017-10-02T19:34:27Z" | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "5juljGXPkBWENR2Os7dlnPQER48=", | 			"checksumSHA1": "5juljGXPkBWENR2Os7dlnPQER48=", | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user