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 | ||||
| func (l *s3Objects) AnonGetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error { | ||||
| 	r := minio.NewGetReqHeaders() | ||||
| 	if err := r.SetRange(startOffset, startOffset+length-1); err != nil { | ||||
| 	opts := minio.GetObjectOptions{} | ||||
| 	if err := opts.SetRange(startOffset, startOffset+length-1); err != nil { | ||||
| 		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 { | ||||
| 		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 | ||||
| func (l *s3Objects) AnonGetObjectInfo(bucket string, object string) (objInfo ObjectInfo, e error) { | ||||
| 	r := minio.NewHeadReqHeaders() | ||||
| 	oi, err := l.anonClient.StatObject(bucket, object, r) | ||||
| 	oi, err := l.anonClient.StatObject(bucket, object, minio.StatObjectOptions{}) | ||||
| 	if err != nil { | ||||
| 		return objInfo, s3ToObjectError(traceError(err), bucket, object) | ||||
| 	} | ||||
|  | ||||
| @ -19,7 +19,6 @@ package cmd | ||||
| import ( | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"encoding/hex" | ||||
| 
 | ||||
| @ -290,22 +289,20 @@ func fromMinioClientListBucketResult(bucket string, result minio.ListBucketResul | ||||
| // startOffset indicates the starting read location 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 { | ||||
| 	r := minio.NewGetReqHeaders() | ||||
| 
 | ||||
| 	if length < 0 && length != -1 { | ||||
| 		return s3ToObjectError(traceError(errInvalidArgument), bucket, key) | ||||
| 	} | ||||
| 
 | ||||
| 	opts := minio.GetObjectOptions{} | ||||
| 	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) | ||||
| 		} | ||||
| 	} | ||||
| 	object, _, err := l.Client.GetObject(bucket, key, r) | ||||
| 	object, _, err := l.Client.GetObject(bucket, key, opts) | ||||
| 	if err != nil { | ||||
| 		return s3ToObjectError(traceError(err), bucket, key) | ||||
| 	} | ||||
| 
 | ||||
| 	defer object.Close() | ||||
| 
 | ||||
| 	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 | ||||
| func (l *s3Objects) GetObjectInfo(bucket string, object string) (objInfo ObjectInfo, err error) { | ||||
| 	r := minio.NewHeadReqHeaders() | ||||
| 	oi, err := l.Client.StatObject(bucket, object, r) | ||||
| 	oi, err := l.Client.StatObject(bucket, object, minio.StatObjectOptions{}) | ||||
| 	if err != nil { | ||||
| 		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 | ||||
| } | ||||
| 
 | ||||
| // 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) { | ||||
| 	// Source object | ||||
| 	src := minio.NewSourceInfo(srcBucket, srcObject, nil) | ||||
| 
 | ||||
| 	// Destination object | ||||
| 	var xamzMeta = map[string]string{} | ||||
| 	for key := range metadata { | ||||
| 		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 { | ||||
| 	// Set this header such that following CopyObject() always sets the right metadata on the destination. | ||||
| 	// 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. | ||||
| 	// So preserve it by adding "REPLACE" directive to save all the metadata set by CopyObject API. | ||||
| 	metadata["x-amz-metadata-directive"] = "REPLACE" | ||||
| 	if _, err = l.Client.CopyObject(srcBucket, srcObject, dstBucket, dstObject, metadata); err != nil { | ||||
| 		return objInfo, s3ToObjectError(traceError(err), srcBucket, srcObject) | ||||
| 	} | ||||
| 
 | ||||
| 	oi, err := l.GetObjectInfo(dstBucket, dstObject) | ||||
| 	if err != nil { | ||||
| 		return objInfo, s3ToObjectError(traceError(err), dstBucket, dstObject) | ||||
| 	} | ||||
| 
 | ||||
| 	return oi, nil | ||||
| 	return l.GetObjectInfo(dstBucket, dstObject) | ||||
| } | ||||
| 
 | ||||
| // DeleteObject deletes a blob in bucket | ||||
|  | ||||
| @ -398,7 +398,9 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | ||||
| 	if err != nil { | ||||
| 		errorIf(err, "found invalid http request header") | ||||
| 		writeErrorResponse(w, ErrInternalError, r.URL) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Check if x-amz-metadata-directive was not set to REPLACE and source, | ||||
| 	// desination are same objects. | ||||
| 	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 | ||||
| * [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) | ||||
| * [fput-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/fputencrypted-object.go) | ||||
| 
 | ||||
| ### Full Examples : Presigned Operations | ||||
| * [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 | ||||
| 	// headers are added to the stat request if given. | ||||
| 	var objInfo ObjectInfo | ||||
| 	rh := NewGetReqHeaders() | ||||
| 	opts := StatObjectOptions{} | ||||
| 	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 { | ||||
| 		err = fmt.Errorf("Could not stat object - %s/%s: %v", s.bucket, s.object, err) | ||||
| 	} else { | ||||
| @ -266,6 +266,51 @@ func (s *SourceInfo) getProps(c Client) (size int64, etag string, userMeta map[s | ||||
| 	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 | ||||
| // upload via an upload-part-copy request | ||||
| // 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" | ||||
| 
 | ||||
| // GetObjectWithContext - returns an seekable, readable object. | ||||
| func (c Client) GetObjectWithContext(ctx context.Context, bucketName, objectName string) (*Object, error) { | ||||
| 	return c.getObjectWithContext(ctx, bucketName, objectName) | ||||
| // The options can be used to specify the GET request further. | ||||
| 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" | ||||
| 	"path/filepath" | ||||
| 
 | ||||
| 	"github.com/minio/minio-go/pkg/encrypt" | ||||
| 
 | ||||
| 	"context" | ||||
| 
 | ||||
| 	"github.com/minio/minio-go/pkg/s3utils" | ||||
| ) | ||||
| 
 | ||||
| // FGetObjectWithContext - download contents of an object to a local file. | ||||
| func (c Client) FGetObjectWithContext(ctx context.Context, bucketName, objectName, filePath string) error { | ||||
| 	return c.fGetObjectWithContext(ctx, bucketName, objectName, filePath) | ||||
| // The options can be used to specify the GET request further. | ||||
| 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. | ||||
| func (c Client) FGetObject(bucketName, objectName, filePath string) error { | ||||
| 	return c.fGetObjectWithContext(context.Background(), bucketName, objectName, filePath) | ||||
| func (c Client) FGetObject(bucketName, objectName, filePath string, opts GetObjectOptions) error { | ||||
| 	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 | ||||
| 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. | ||||
| 	if err := s3utils.CheckValidBucketName(bucketName); err != nil { | ||||
| 		return err | ||||
| @ -72,7 +83,7 @@ func (c Client) fGetObjectWithContext(ctx context.Context, bucketName, objectNam | ||||
| 	} | ||||
| 
 | ||||
| 	// Gather md5sum. | ||||
| 	objectStat, err := c.StatObject(bucketName, objectName) | ||||
| 	objectStat, err := c.StatObject(bucketName, objectName, StatObjectOptions{opts}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @ -94,13 +105,12 @@ func (c Client) fGetObjectWithContext(ctx context.Context, bucketName, objectNam | ||||
| 
 | ||||
| 	// Initialize get object request headers to set the | ||||
| 	// appropriate range offsets to read from. | ||||
| 	reqHeaders := NewGetReqHeaders() | ||||
| 	if st.Size() > 0 { | ||||
| 		reqHeaders.SetRange(st.Size(), 0) | ||||
| 		opts.SetRange(st.Size(), 0) | ||||
| 	} | ||||
| 
 | ||||
| 	// 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 { | ||||
| 		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") | ||||
| 	} | ||||
| 
 | ||||
| 	// Fetch encrypted object | ||||
| 	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 | ||||
| 	return c.GetObject(bucketName, objectName, GetObjectOptions{Materials: encryptMaterials}) | ||||
| } | ||||
| 
 | ||||
| // GetObject - returns an seekable, readable object. | ||||
| func (c Client) GetObject(bucketName, objectName string) (*Object, error) { | ||||
| 	return c.getObjectWithContext(context.Background(), bucketName, objectName) | ||||
| func (c Client) GetObject(bucketName, objectName string, opts GetObjectOptions) (*Object, error) { | ||||
| 	return c.getObjectWithContext(context.Background(), bucketName, objectName, opts) | ||||
| } | ||||
| 
 | ||||
| // 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. | ||||
| 	if err := s3utils.CheckValidBucketName(bucketName); err != nil { | ||||
| 		return nil, err | ||||
| @ -108,34 +92,26 @@ func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName | ||||
| 				if req.isFirstReq { | ||||
| 					// First request is a Read/ReadAt. | ||||
| 					if req.isReadOp { | ||||
| 						reqHeaders := NewGetReqHeaders() | ||||
| 						// Differentiate between wanting the whole object and just a range. | ||||
| 						if req.isReadAt { | ||||
| 							// 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. | ||||
| 							// Do not set objectInfo from the first readAt request because it will not get | ||||
| 							// the whole object. | ||||
| 							reqHeaders.SetRange(req.Offset, req.Offset+int64(len(req.Buffer))-1) | ||||
| 							httpReader, objectInfo, err = c.getObject(ctx, bucketName, objectName, reqHeaders) | ||||
| 						} else { | ||||
| 							if req.Offset > 0 { | ||||
| 								reqHeaders.SetRange(req.Offset, 0) | ||||
| 							} | ||||
| 
 | ||||
| 							// First request is a Read request. | ||||
| 							httpReader, objectInfo, err = c.getObject(ctx, bucketName, objectName, reqHeaders) | ||||
| 							opts.SetRange(req.Offset, req.Offset+int64(len(req.Buffer))-1) | ||||
| 						} else if req.Offset > 0 { | ||||
| 							opts.SetRange(req.Offset, 0) | ||||
| 						} | ||||
| 						httpReader, objectInfo, err = c.getObject(ctx, bucketName, objectName, opts) | ||||
| 						if err != nil { | ||||
| 							resCh <- getResponse{ | ||||
| 								Error: err, | ||||
| 							} | ||||
| 							resCh <- getResponse{Error: err} | ||||
| 							return | ||||
| 						} | ||||
| 						etag = objectInfo.ETag | ||||
| 						// Read at least firstReq.Buffer bytes, if not we have | ||||
| 						// reached our EOF. | ||||
| 						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 | ||||
| 							// all the bytes ReadFull returns ErrUnexpectedEOF | ||||
| 							err = io.EOF | ||||
| @ -150,7 +126,7 @@ func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName | ||||
| 					} else { | ||||
| 						// First request is a Stat or Seek call. | ||||
| 						// 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 { | ||||
| 							resCh <- getResponse{ | ||||
| 								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. | ||||
| 					reqHeaders := NewGetReqHeaders() | ||||
| 					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 { | ||||
| 						resCh <- getResponse{ | ||||
| 							Error: err, | ||||
| @ -189,9 +164,8 @@ func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName | ||||
| 					// new ones when they haven't been already. | ||||
| 					// All readAt requests are new requests. | ||||
| 					if req.DidOffsetChange || !req.beenRead { | ||||
| 						reqHeaders := NewGetReqHeaders() | ||||
| 						if etag != "" { | ||||
| 							reqHeaders.SetMatchETag(etag) | ||||
| 							opts.SetMatchETag(etag) | ||||
| 						} | ||||
| 						if httpReader != nil { | ||||
| 							// 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 req.isReadAt { | ||||
| 							// 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) | ||||
| 							httpReader, _, err = c.getObject(ctx, bucketName, objectName, reqHeaders) | ||||
| 						} else { | ||||
| 							// 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) | ||||
| 							opts.SetRange(req.Offset, req.Offset+int64(len(req.Buffer))-1) | ||||
| 						} else if req.Offset > 0 { // Range is set with respect to the offset. | ||||
| 							opts.SetRange(req.Offset, 0) | ||||
| 						} | ||||
| 						httpReader, objectInfo, err = c.getObject(ctx, bucketName, objectName, opts) | ||||
| 						if err != nil { | ||||
| 							resCh <- getResponse{ | ||||
| 								Error: err, | ||||
| @ -632,7 +601,7 @@ func newObject(reqCh chan<- getRequest, resCh <-chan getResponse, doneCh chan<- | ||||
| // | ||||
| // For more information about the HTTP Range header. | ||||
| // 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. | ||||
| 	if err := s3utils.CheckValidBucketName(bucketName); err != nil { | ||||
| 		return nil, ObjectInfo{}, err | ||||
| @ -641,17 +610,11 @@ func (c Client) getObject(ctx context.Context, bucketName, objectName string, re | ||||
| 		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. | ||||
| 	resp, err := c.executeMethod(ctx, "GET", requestMetadata{ | ||||
| 		bucketName:         bucketName, | ||||
| 		objectName:         objectName, | ||||
| 		customHeader:       customHeader, | ||||
| 		customHeader:       opts.Header(), | ||||
| 		contentSHA256Bytes: emptySHA256, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| @ -698,6 +661,15 @@ func (c Client) getObject(ctx context.Context, bucketName, objectName string, re | ||||
| 		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 | ||||
| 	return resp.Body, objectStat, nil | ||||
| 	return reader, objectStat, nil | ||||
| } | ||||
|  | ||||
| @ -20,80 +20,94 @@ import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/minio/minio-go/pkg/encrypt" | ||||
| ) | ||||
| 
 | ||||
| // RequestHeaders - implement methods for setting special | ||||
| // request headers for GET, HEAD object operations. | ||||
| // http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html | ||||
| type RequestHeaders struct { | ||||
| 	http.Header | ||||
| // GetObjectOptions are used to specify additional headers or options | ||||
| // during GET requests. | ||||
| type GetObjectOptions struct { | ||||
| 	headers map[string]string | ||||
| 
 | ||||
| 	Materials encrypt.Materials | ||||
| } | ||||
| 
 | ||||
| // NewGetReqHeaders - initializes a new request headers for GET request. | ||||
| func NewGetReqHeaders() RequestHeaders { | ||||
| 	return RequestHeaders{ | ||||
| 		Header: make(http.Header), | ||||
| 	} | ||||
| // StatObjectOptions are used to specify additional headers or options | ||||
| // during GET info/stat requests. | ||||
| type StatObjectOptions struct { | ||||
| 	GetObjectOptions | ||||
| } | ||||
| 
 | ||||
| // NewHeadReqHeaders - initializes a new request headers for HEAD request. | ||||
| func NewHeadReqHeaders() RequestHeaders { | ||||
| 	return RequestHeaders{ | ||||
| 		Header: make(http.Header), | ||||
| // Header returns the http.Header representation of the GET options. | ||||
| func (o GetObjectOptions) Header() http.Header { | ||||
| 	headers := make(http.Header, len(o.headers)) | ||||
| 	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. | ||||
| func (c RequestHeaders) SetMatchETag(etag string) error { | ||||
| func (o *GetObjectOptions) SetMatchETag(etag string) error { | ||||
| 	if etag == "" { | ||||
| 		return ErrInvalidArgument("ETag cannot be empty.") | ||||
| 	} | ||||
| 	c.Set("If-Match", "\""+etag+"\"") | ||||
| 	o.Set("If-Match", "\""+etag+"\"") | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // SetMatchETagExcept - set match etag except. | ||||
| func (c RequestHeaders) SetMatchETagExcept(etag string) error { | ||||
| func (o *GetObjectOptions) SetMatchETagExcept(etag string) error { | ||||
| 	if etag == "" { | ||||
| 		return ErrInvalidArgument("ETag cannot be empty.") | ||||
| 	} | ||||
| 	c.Set("If-None-Match", "\""+etag+"\"") | ||||
| 	o.Set("If-None-Match", "\""+etag+"\"") | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // SetUnmodified - set unmodified time since. | ||||
| func (c RequestHeaders) SetUnmodified(modTime time.Time) error { | ||||
| func (o *GetObjectOptions) SetUnmodified(modTime time.Time) error { | ||||
| 	if modTime.IsZero() { | ||||
| 		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 | ||||
| } | ||||
| 
 | ||||
| // SetModified - set modified time since. | ||||
| func (c RequestHeaders) SetModified(modTime time.Time) error { | ||||
| func (o *GetObjectOptions) SetModified(modTime time.Time) error { | ||||
| 	if modTime.IsZero() { | ||||
| 		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 | ||||
| } | ||||
| 
 | ||||
| // 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. | ||||
| func (c RequestHeaders) SetRange(start, end int64) error { | ||||
| func (o *GetObjectOptions) SetRange(start, end int64) error { | ||||
| 	switch { | ||||
| 	case start == 0 && end < 0: | ||||
| 		// 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: | ||||
| 		// Read everything starting from offset | ||||
| 		// 'start'. `bytes=N-`. | ||||
| 		c.Set("Range", fmt.Sprintf("bytes=%d-", start)) | ||||
| 		o.Set("Range", fmt.Sprintf("bytes=%d-", start)) | ||||
| 	case 0 <= start && start <= end: | ||||
| 		// Read everything starting at 'start' till the | ||||
| 		// '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: | ||||
| 		// All other cases such as | ||||
| 		// 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. | ||||
| type copyObjectResult struct { | ||||
| 	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. | ||||
|  | ||||
							
								
								
									
										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. | ||||
| func (c Client) StatObject(bucketName, objectName string) (ObjectInfo, error) { | ||||
| func (c Client) StatObject(bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) { | ||||
| 	// Input validation. | ||||
| 	if err := s3utils.CheckValidBucketName(bucketName); err != nil { | ||||
| 		return ObjectInfo{}, err | ||||
| @ -89,12 +89,11 @@ func (c Client) StatObject(bucketName, objectName string) (ObjectInfo, error) { | ||||
| 	if err := s3utils.CheckValidObjectName(objectName); err != nil { | ||||
| 		return ObjectInfo{}, err | ||||
| 	} | ||||
| 	reqHeaders := NewHeadReqHeaders() | ||||
| 	return c.statObject(bucketName, objectName, reqHeaders) | ||||
| 	return c.statObject(bucketName, objectName, opts) | ||||
| } | ||||
| 
 | ||||
| // 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. | ||||
| 	if err := s3utils.CheckValidBucketName(bucketName); err != nil { | ||||
| 		return ObjectInfo{}, err | ||||
| @ -103,17 +102,12 @@ func (c Client) statObject(bucketName, objectName string, reqHeaders RequestHead | ||||
| 		return ObjectInfo{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	customHeader := make(http.Header) | ||||
| 	for k, v := range reqHeaders.Header { | ||||
| 		customHeader[k] = v | ||||
| 	} | ||||
| 
 | ||||
| 	// Execute HEAD on objectName. | ||||
| 	resp, err := c.executeMethod(context.Background(), "HEAD", requestMetadata{ | ||||
| 		bucketName:         bucketName, | ||||
| 		objectName:         objectName, | ||||
| 		contentSHA256Bytes: emptySHA256, | ||||
| 		customHeader:       customHeader, | ||||
| 		customHeader:       opts.Header(), | ||||
| 	}) | ||||
| 	defer closeResponse(resp) | ||||
| 	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 ( | ||||
| 	"context" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"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) | ||||
| } | ||||
| 
 | ||||
| // 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. | ||||
| 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. | ||||
| @ -111,12 +133,12 @@ func (c Core) PutBucketPolicy(bucket string, bucketPolicy policy.BucketAccessPol | ||||
| // GetObject is a lower level API implemented to support reading | ||||
| // partial objects and also downloading objects with special conditions | ||||
| // matching etag, modtime etc. | ||||
| func (c Core) GetObject(bucketName, objectName string, reqHeaders RequestHeaders) (io.ReadCloser, ObjectInfo, error) { | ||||
| 	return c.getObject(context.Background(), bucketName, objectName, reqHeaders) | ||||
| func (c Core) GetObject(bucketName, objectName string, opts GetObjectOptions) (io.ReadCloser, ObjectInfo, error) { | ||||
| 	return c.getObject(context.Background(), bucketName, objectName, opts) | ||||
| } | ||||
| 
 | ||||
| // StatObject is a lower level API implemented to support special | ||||
| // conditions matching etag, modtime on a request. | ||||
| func (c Core) StatObject(bucketName, objectName string, reqHeaders RequestHeaders) (ObjectInfo, error) { | ||||
| 	return c.statObject(bucketName, objectName, reqHeaders) | ||||
| func (c Core) StatObject(bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) { | ||||
| 	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" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"checksumSHA1": "mqxOM3CsubB09O0nDEe4efu0JLQ=", | ||||
| 			"checksumSHA1": "EkdIh5Mk2bRiARtdoqUfnBuyndk=", | ||||
| 			"path": "github.com/minio/minio-go", | ||||
| 			"revision": "414c6b6a2e97428776cd831d9745589ebcf873e5", | ||||
| 			"revisionTime": "2017-09-27T19:03:45Z" | ||||
| 			"revision": "9690dc6c40e6ef271727848c04f974e801212ac1", | ||||
| 			"revisionTime": "2017-10-02T19:34:27Z" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"checksumSHA1": "5juljGXPkBWENR2Os7dlnPQER48=", | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user