| 
									
										
										
										
											2015-02-24 08:46:48 +08:00
										 |  |  |  | /* | 
					
						
							| 
									
										
										
										
											2019-04-10 02:39:42 +08:00
										 |  |  |  |  * MinIO Cloud Storage, (C) 2015-2018 MinIO, Inc. | 
					
						
							| 
									
										
										
										
											2015-02-24 08:46:48 +08:00
										 |  |  |  |  * | 
					
						
							|  |  |  |  |  * 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, | 
					
						
							| 
									
										
										
											
												objectAPI: Fix object API interface, remove unnecessary structs.
ObjectAPI changes.
```
ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, *probe.Error)
ListMultipartUploads(bucket, objectPrefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, *probe.Error)
ListObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (ListPartsInfo, *probe.Error)
CompleteMultipartUpload(bucket string, object string, uploadID string, parts []completePart) (ObjectInfo, *probe.Error)
```
											
										 
											2016-04-03 16:34:20 +08:00
										 |  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							| 
									
										
										
										
											2015-02-24 08:46:48 +08:00
										 |  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  |  */ | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-19 07:23:42 +08:00
										 |  |  |  | package cmd | 
					
						
							| 
									
										
										
										
											2015-02-16 09:03:27 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2018-04-06 06:04:40 +08:00
										 |  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 	"crypto/hmac" | 
					
						
							|  |  |  |  | 	"encoding/binary" | 
					
						
							| 
									
										
										
										
											2016-03-13 08:08:15 +08:00
										 |  |  |  | 	"encoding/hex" | 
					
						
							| 
									
										
											  
											
												fs: Break fs package to top-level and introduce ObjectAPI interface.
ObjectAPI interface brings in changes needed for XL ObjectAPI layer.
The new interface for any ObjectAPI layer is as below
```
// ObjectAPI interface.
type ObjectAPI interface {
        // Bucket resource API.
        DeleteBucket(bucket string) *probe.Error
        ListBuckets() ([]BucketInfo, *probe.Error)
        MakeBucket(bucket string) *probe.Error
        GetBucketInfo(bucket string) (BucketInfo, *probe.Error)
        // Bucket query API.
        ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error)
        ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error)
        // Object resource API.
        GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error)
        GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error)
        PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error)
        DeleteObject(bucket, object string) *probe.Error
        // Object query API.
        NewMultipartUpload(bucket, object string) (string, *probe.Error)
        PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error)
        ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error)
        CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error)
        AbortMultipartUpload(bucket, object, uploadID string) *probe.Error
}
```
											
										 
											2016-03-31 07:15:28 +08:00
										 |  |  |  | 	"encoding/xml" | 
					
						
							| 
									
										
										
										
											2017-11-08 07:18:59 +08:00
										 |  |  |  | 	"io" | 
					
						
							|  |  |  |  | 	goioutil "io/ioutil" | 
					
						
							| 
									
										
										
										
											2015-02-16 09:03:27 +08:00
										 |  |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2016-02-07 19:37:54 +08:00
										 |  |  |  | 	"net/url" | 
					
						
							| 
									
										
											  
											
												fs: Break fs package to top-level and introduce ObjectAPI interface.
ObjectAPI interface brings in changes needed for XL ObjectAPI layer.
The new interface for any ObjectAPI layer is as below
```
// ObjectAPI interface.
type ObjectAPI interface {
        // Bucket resource API.
        DeleteBucket(bucket string) *probe.Error
        ListBuckets() ([]BucketInfo, *probe.Error)
        MakeBucket(bucket string) *probe.Error
        GetBucketInfo(bucket string) (BucketInfo, *probe.Error)
        // Bucket query API.
        ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error)
        ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error)
        // Object resource API.
        GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error)
        GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error)
        PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error)
        DeleteObject(bucket, object string) *probe.Error
        // Object query API.
        NewMultipartUpload(bucket, object string) (string, *probe.Error)
        PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error)
        ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error)
        CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error)
        AbortMultipartUpload(bucket, object, uploadID string) *probe.Error
}
```
											
										 
											2016-03-31 07:15:28 +08:00
										 |  |  |  | 	"sort" | 
					
						
							| 
									
										
										
										
											2015-05-04 14:16:10 +08:00
										 |  |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2015-02-16 09:03:27 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-06 12:58:09 +08:00
										 |  |  |  | 	"time" | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-22 10:23:54 +08:00
										 |  |  |  | 	"github.com/gorilla/mux" | 
					
						
							| 
									
										
										
										
											2019-05-30 07:35:12 +08:00
										 |  |  |  | 	miniogo "github.com/minio/minio-go/v6" | 
					
						
							|  |  |  |  | 	"github.com/minio/minio-go/v6/pkg/encrypt" | 
					
						
							| 
									
										
										
										
											2018-08-18 03:52:14 +08:00
										 |  |  |  | 	"github.com/minio/minio/cmd/crypto" | 
					
						
							| 
									
										
										
										
											2018-04-06 06:04:40 +08:00
										 |  |  |  | 	"github.com/minio/minio/cmd/logger" | 
					
						
							| 
									
										
										
										
											2018-05-16 09:20:22 +08:00
										 |  |  |  | 	"github.com/minio/minio/pkg/dns" | 
					
						
							| 
									
										
										
										
											2018-03-16 04:03:41 +08:00
										 |  |  |  | 	"github.com/minio/minio/pkg/event" | 
					
						
							| 
									
										
										
										
											2018-07-03 05:40:18 +08:00
										 |  |  |  | 	"github.com/minio/minio/pkg/handlers" | 
					
						
							| 
									
										
										
										
											2017-10-22 13:30:34 +08:00
										 |  |  |  | 	"github.com/minio/minio/pkg/hash" | 
					
						
							| 
									
										
										
										
											2017-11-08 07:18:59 +08:00
										 |  |  |  | 	"github.com/minio/minio/pkg/ioutil" | 
					
						
							| 
									
										
										
										
											2018-04-25 06:53:30 +08:00
										 |  |  |  | 	"github.com/minio/minio/pkg/policy" | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | 	"github.com/minio/minio/pkg/s3select" | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 	sha256 "github.com/minio/sha256-simd" | 
					
						
							|  |  |  |  | 	"github.com/minio/sio" | 
					
						
							| 
									
										
										
										
											2015-05-10 10:39:00 +08:00
										 |  |  |  | ) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-09 02:04:04 +08:00
										 |  |  |  | // supportedHeadGetReqParams - supported request parameters for GET and HEAD presigned request.
 | 
					
						
							|  |  |  |  | var supportedHeadGetReqParams = map[string]string{ | 
					
						
							| 
									
										
										
										
											2016-02-07 19:37:54 +08:00
										 |  |  |  | 	"response-expires":             "Expires", | 
					
						
							|  |  |  |  | 	"response-content-type":        "Content-Type", | 
					
						
							|  |  |  |  | 	"response-cache-control":       "Cache-Control", | 
					
						
							| 
									
										
										
										
											2016-08-11 08:36:28 +08:00
										 |  |  |  | 	"response-content-encoding":    "Content-Encoding", | 
					
						
							|  |  |  |  | 	"response-content-language":    "Content-Language", | 
					
						
							| 
									
										
										
										
											2016-02-07 19:37:54 +08:00
										 |  |  |  | 	"response-content-disposition": "Content-Disposition", | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | const ( | 
					
						
							|  |  |  |  | 	compressionAlgorithmV1 = "golang/snappy/LZ77" | 
					
						
							|  |  |  |  | ) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-09 02:04:04 +08:00
										 |  |  |  | // setHeadGetRespHeaders - set any requested parameters as response headers.
 | 
					
						
							|  |  |  |  | func setHeadGetRespHeaders(w http.ResponseWriter, reqParams url.Values) { | 
					
						
							| 
									
										
										
										
											2016-02-07 19:37:54 +08:00
										 |  |  |  | 	for k, v := range reqParams { | 
					
						
							| 
									
										
										
										
											2017-08-09 02:04:04 +08:00
										 |  |  |  | 		if header, ok := supportedHeadGetReqParams[k]; ok { | 
					
						
							| 
									
										
										
										
											2016-02-07 19:37:54 +08:00
										 |  |  |  | 			w.Header()[header] = v | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | // SelectObjectContentHandler - GET Object?select
 | 
					
						
							|  |  |  |  | // ----------
 | 
					
						
							|  |  |  |  | // This implementation of the GET operation retrieves object content based
 | 
					
						
							|  |  |  |  | // on an SQL expression. In the request, along with the sql expression, you must
 | 
					
						
							|  |  |  |  | // also specify a data serialization format (JSON, CSV) of the object.
 | 
					
						
							|  |  |  |  | func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  |  | 	ctx := newContext(r, w, "SelectObject") | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-22 12:03:24 +08:00
										 |  |  |  | 	defer logger.AuditLog(w, r, "SelectObject", mustGetClaimsFromToken(r)) | 
					
						
							| 
									
										
										
										
											2018-10-13 03:25:59 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | 	// Fetch object stat info.
 | 
					
						
							|  |  |  |  | 	objectAPI := api.ObjectAPI() | 
					
						
							|  |  |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-10-10 06:04:53 +08:00
										 |  |  |  | 	if crypto.S3KMS.IsRequested(r.Header) { // SSE-KMS is not supported
 | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-10 06:04:53 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if !api.EncryptionEnabled() && hasServerSideEncryptionHeader(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-12-15 13:39:59 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 	vars := mux.Vars(r) | 
					
						
							|  |  |  |  | 	bucket := vars["bucket"] | 
					
						
							|  |  |  |  | 	object := vars["object"] | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	// get gateway encryption options
 | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 	opts, err := getOpts(ctx, r, bucket, object) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 		writeErrorResponseHeadersOnly(w, toAPIError(ctx, err)) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-09 08:53:04 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	getObjectInfo := objectAPI.GetObjectInfo | 
					
						
							|  |  |  |  | 	if api.CacheAPI() != nil { | 
					
						
							|  |  |  |  | 		getObjectInfo = api.CacheAPI().GetObjectInfo | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 	// Check for auth type to return S3 compatible error.
 | 
					
						
							|  |  |  |  | 	// type to return the correct error (NoSuchKey vs AccessDenied)
 | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | 	if s3Error := checkRequestAuthType(ctx, r, policy.GetObjectAction, bucket, object); s3Error != ErrNone { | 
					
						
							|  |  |  |  | 		if getRequestAuthType(r) == authTypeAnonymous { | 
					
						
							|  |  |  |  | 			// As per "Permission" section in
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 			// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html
 | 
					
						
							|  |  |  |  | 			// If the object you request does not exist,
 | 
					
						
							|  |  |  |  | 			// the error Amazon S3 returns depends on
 | 
					
						
							|  |  |  |  | 			// whether you also have the s3:ListBucket
 | 
					
						
							|  |  |  |  | 			// permission.
 | 
					
						
							|  |  |  |  | 			// * If you have the s3:ListBucket permission
 | 
					
						
							|  |  |  |  | 			//   on the bucket, Amazon S3 will return an
 | 
					
						
							|  |  |  |  | 			//   HTTP status code 404 ("no such key")
 | 
					
						
							|  |  |  |  | 			//   error.
 | 
					
						
							|  |  |  |  | 			// * if you don’t have the s3:ListBucket
 | 
					
						
							|  |  |  |  | 			//   permission, Amazon S3 will return an HTTP
 | 
					
						
							|  |  |  |  | 			//   status code 403 ("access denied") error.`
 | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | 			if globalPolicySys.IsAllowed(policy.Args{ | 
					
						
							|  |  |  |  | 				Action:          policy.ListBucketAction, | 
					
						
							|  |  |  |  | 				BucketName:      bucket, | 
					
						
							| 
									
										
										
										
											2019-01-21 12:57:14 +08:00
										 |  |  |  | 				ConditionValues: getConditionValues(r, "", ""), | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | 				IsOwner:         false, | 
					
						
							|  |  |  |  | 			}) { | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 				_, err = getObjectInfo(ctx, bucket, object, opts) | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 				if toAPIError(ctx, err).Code == "NoSuchKey" { | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | 					s3Error = ErrNoSuchKey | 
					
						
							|  |  |  |  | 				} | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// Get request range.
 | 
					
						
							|  |  |  |  | 	rangeHeader := r.Header.Get("Range") | 
					
						
							|  |  |  |  | 	if rangeHeader != "" { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrUnsupportedRangeHeader), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | 	if r.ContentLength <= 0 { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrEmptyRequestBody), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-09 08:53:04 +08:00
										 |  |  |  | 	s3Select, err := s3select.NewS3Select(r.Body) | 
					
						
							|  |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		if serr, ok := err.(s3select.SelectError); ok { | 
					
						
							| 
									
										
										
										
											2019-02-13 15:48:11 +08:00
										 |  |  |  | 			encodedErrorResponse := encodeResponse(APIErrorResponse{ | 
					
						
							|  |  |  |  | 				Code:       serr.ErrorCode(), | 
					
						
							|  |  |  |  | 				Message:    serr.ErrorMessage(), | 
					
						
							|  |  |  |  | 				BucketName: bucket, | 
					
						
							|  |  |  |  | 				Key:        object, | 
					
						
							|  |  |  |  | 				Resource:   r.URL.Path, | 
					
						
							|  |  |  |  | 				RequestID:  w.Header().Get(responseRequestIDKey), | 
					
						
							|  |  |  |  | 				HostID:     w.Header().Get(responseDeploymentIDKey), | 
					
						
							|  |  |  |  | 			}) | 
					
						
							|  |  |  |  | 			writeResponse(w, serr.HTTPStatusCode(), encodedErrorResponse, mimeXML) | 
					
						
							| 
									
										
										
										
											2019-01-09 08:53:04 +08:00
										 |  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2019-01-09 08:53:04 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	getObjectNInfo := objectAPI.GetObjectNInfo | 
					
						
							|  |  |  |  | 	if api.CacheAPI() != nil { | 
					
						
							|  |  |  |  | 		getObjectNInfo = api.CacheAPI().GetObjectNInfo | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-09 08:53:04 +08:00
										 |  |  |  | 	getObject := func(offset, length int64) (rc io.ReadCloser, err error) { | 
					
						
							|  |  |  |  | 		isSuffixLength := false | 
					
						
							|  |  |  |  | 		if offset < 0 { | 
					
						
							|  |  |  |  | 			isSuffixLength = true | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-01-09 08:53:04 +08:00
										 |  |  |  | 		rs := &HTTPRangeSpec{ | 
					
						
							|  |  |  |  | 			IsSuffixLength: isSuffixLength, | 
					
						
							|  |  |  |  | 			Start:          offset, | 
					
						
							| 
									
										
										
										
											2019-01-17 00:22:04 +08:00
										 |  |  |  | 			End:            offset + length, | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-09 08:53:04 +08:00
										 |  |  |  | 		return getObjectNInfo(ctx, bucket, object, rs, r.Header, readLock, ObjectOptions{}) | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-29 09:59:48 +08:00
										 |  |  |  | 	objInfo, err := getObjectInfo(ctx, bucket, object, opts) | 
					
						
							|  |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2019-01-29 09:59:48 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-09 08:53:04 +08:00
										 |  |  |  | 	if err = s3Select.Open(getObject); err != nil { | 
					
						
							|  |  |  |  | 		if serr, ok := err.(s3select.SelectError); ok { | 
					
						
							| 
									
										
										
										
											2019-02-13 15:48:11 +08:00
										 |  |  |  | 			encodedErrorResponse := encodeResponse(APIErrorResponse{ | 
					
						
							|  |  |  |  | 				Code:       serr.ErrorCode(), | 
					
						
							|  |  |  |  | 				Message:    serr.ErrorMessage(), | 
					
						
							|  |  |  |  | 				BucketName: bucket, | 
					
						
							|  |  |  |  | 				Key:        object, | 
					
						
							|  |  |  |  | 				Resource:   r.URL.Path, | 
					
						
							|  |  |  |  | 				RequestID:  w.Header().Get(responseRequestIDKey), | 
					
						
							|  |  |  |  | 				HostID:     w.Header().Get(responseDeploymentIDKey), | 
					
						
							|  |  |  |  | 			}) | 
					
						
							|  |  |  |  | 			writeResponse(w, serr.HTTPStatusCode(), encodedErrorResponse, mimeXML) | 
					
						
							| 
									
										
										
										
											2019-01-09 08:53:04 +08:00
										 |  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-11-15 07:55:10 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-10-23 03:12:22 +08:00
										 |  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-10-23 03:12:22 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-09 08:53:04 +08:00
										 |  |  |  | 	s3Select.Evaluate(w) | 
					
						
							|  |  |  |  | 	s3Select.Close() | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-03 09:40:08 +08:00
										 |  |  |  | 	// Notify object accessed via a GET request.
 | 
					
						
							|  |  |  |  | 	sendEvent(eventArgs{ | 
					
						
							|  |  |  |  | 		EventName:    event.ObjectAccessedGet, | 
					
						
							|  |  |  |  | 		BucketName:   bucket, | 
					
						
							|  |  |  |  | 		Object:       objInfo, | 
					
						
							|  |  |  |  | 		ReqParams:    extractReqParams(r), | 
					
						
							|  |  |  |  | 		RespElements: extractRespElements(w), | 
					
						
							|  |  |  |  | 		UserAgent:    r.UserAgent(), | 
					
						
							| 
									
										
										
										
											2019-03-26 02:45:42 +08:00
										 |  |  |  | 		Host:         handlers.GetSourceIP(r), | 
					
						
							| 
									
										
										
										
											2018-11-03 09:40:08 +08:00
										 |  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2018-08-15 18:30:19 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-07-01 11:15:48 +08:00
										 |  |  |  | // GetObjectHandler - GET Object
 | 
					
						
							| 
									
										
										
										
											2015-02-24 08:46:48 +08:00
										 |  |  |  | // ----------
 | 
					
						
							|  |  |  |  | // This implementation of the GET operation retrieves object. To use GET,
 | 
					
						
							|  |  |  |  | // you must have READ access to the object.
 | 
					
						
							| 
									
										
										
										
											2016-04-13 03:45:15 +08:00
										 |  |  |  | func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2018-07-21 09:46:32 +08:00
										 |  |  |  | 	ctx := newContext(r, w, "GetObject") | 
					
						
							| 
									
										
										
										
											2018-03-15 03:01:47 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-22 12:03:24 +08:00
										 |  |  |  | 	defer logger.AuditLog(w, r, "GetObject", mustGetClaimsFromToken(r)) | 
					
						
							| 
									
										
										
										
											2018-10-13 03:25:59 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 	objectAPI := api.ObjectAPI() | 
					
						
							|  |  |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 	if crypto.S3.IsRequested(r.Header) || crypto.S3KMS.IsRequested(r.Header) { // If SSE-S3 or SSE-KMS present -> AWS fails with undefined error
 | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if !api.EncryptionEnabled() && hasServerSideEncryptionHeader(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-12-15 13:39:59 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 	vars := mux.Vars(r) | 
					
						
							|  |  |  |  | 	bucket := vars["bucket"] | 
					
						
							|  |  |  |  | 	object := vars["object"] | 
					
						
							| 
									
										
										
										
											2018-09-01 04:10:12 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-13 03:43:44 +08:00
										 |  |  |  | 	if vid := r.URL.Query().Get("versionId"); vid != "" && vid != "null" { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNoSuchVersion), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-12-13 03:43:44 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	// get gateway encryption options
 | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 	opts, err := getOpts(ctx, r, bucket, object) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 		writeErrorResponseHeadersOnly(w, toAPIError(ctx, err)) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 	// Check for auth type to return S3 compatible error.
 | 
					
						
							|  |  |  |  | 	// type to return the correct error (NoSuchKey vs AccessDenied)
 | 
					
						
							| 
									
										
										
										
											2018-05-02 14:43:27 +08:00
										 |  |  |  | 	if s3Error := checkRequestAuthType(ctx, r, policy.GetObjectAction, bucket, object); s3Error != ErrNone { | 
					
						
							|  |  |  |  | 		if getRequestAuthType(r) == authTypeAnonymous { | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 			// As per "Permission" section in
 | 
					
						
							|  |  |  |  | 			// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html
 | 
					
						
							|  |  |  |  | 			// If the object you request does not exist,
 | 
					
						
							|  |  |  |  | 			// the error Amazon S3 returns depends on
 | 
					
						
							|  |  |  |  | 			// whether you also have the s3:ListBucket
 | 
					
						
							|  |  |  |  | 			// permission.
 | 
					
						
							|  |  |  |  | 			// * If you have the s3:ListBucket permission
 | 
					
						
							|  |  |  |  | 			//   on the bucket, Amazon S3 will return an
 | 
					
						
							|  |  |  |  | 			//   HTTP status code 404 ("no such key")
 | 
					
						
							|  |  |  |  | 			//   error.
 | 
					
						
							|  |  |  |  | 			// * if you don’t have the s3:ListBucket
 | 
					
						
							|  |  |  |  | 			//   permission, Amazon S3 will return an HTTP
 | 
					
						
							|  |  |  |  | 			//   status code 403 ("access denied") error.`
 | 
					
						
							| 
									
										
										
										
											2018-05-02 14:43:27 +08:00
										 |  |  |  | 			if globalPolicySys.IsAllowed(policy.Args{ | 
					
						
							| 
									
										
										
										
											2018-04-25 06:53:30 +08:00
										 |  |  |  | 				Action:          policy.ListBucketAction, | 
					
						
							|  |  |  |  | 				BucketName:      bucket, | 
					
						
							| 
									
										
										
										
											2019-01-21 12:57:14 +08:00
										 |  |  |  | 				ConditionValues: getConditionValues(r, "", ""), | 
					
						
							| 
									
										
										
										
											2018-04-25 06:53:30 +08:00
										 |  |  |  | 				IsOwner:         false, | 
					
						
							|  |  |  |  | 			}) { | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 				getObjectInfo := objectAPI.GetObjectInfo | 
					
						
							|  |  |  |  | 				if api.CacheAPI() != nil { | 
					
						
							|  |  |  |  | 					getObjectInfo = api.CacheAPI().GetObjectInfo | 
					
						
							|  |  |  |  | 				} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 				_, err = getObjectInfo(ctx, bucket, object, opts) | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 				if toAPIError(ctx, err).Code == "NoSuchKey" { | 
					
						
							| 
									
										
										
										
											2018-05-02 14:43:27 +08:00
										 |  |  |  | 					s3Error = ErrNoSuchKey | 
					
						
							|  |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-04-25 06:53:30 +08:00
										 |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2015-08-04 07:17:21 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-05-02 14:43:27 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-08-29 04:08:30 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 	getObjectNInfo := objectAPI.GetObjectNInfo | 
					
						
							|  |  |  |  | 	if api.CacheAPI() != nil { | 
					
						
							|  |  |  |  | 		getObjectNInfo = api.CacheAPI().GetObjectNInfo | 
					
						
							| 
									
										
										
										
											2018-08-29 04:08:30 +08:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// Get request range.
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 	var rs *HTTPRangeSpec | 
					
						
							| 
									
										
										
										
											2018-08-29 04:08:30 +08:00
										 |  |  |  | 	rangeHeader := r.Header.Get("Range") | 
					
						
							|  |  |  |  | 	if rangeHeader != "" { | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 		if rs, err = parseRequestRangeSpec(rangeHeader); err != nil { | 
					
						
							|  |  |  |  | 			// Handle only errInvalidRange. Ignore other
 | 
					
						
							|  |  |  |  | 			// parse error and treat it as regular Get
 | 
					
						
							|  |  |  |  | 			// request like Amazon S3.
 | 
					
						
							| 
									
										
										
										
											2018-08-29 04:08:30 +08:00
										 |  |  |  | 			if err == errInvalidRange { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRange), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-08-29 04:08:30 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 			logger.LogIf(ctx, err) | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-27 18:06:45 +08:00
										 |  |  |  | 	gr, err := getObjectNInfo(ctx, bucket, object, rs, r.Header, readLock, opts) | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-06-27 09:10:08 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 	defer gr.Close() | 
					
						
							| 
									
										
										
										
											2018-10-31 11:18:01 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 	objInfo := gr.ObjInfo | 
					
						
							| 
									
										
										
										
											2016-06-27 09:10:08 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 	if objectAPI.IsEncryptionSupported() { | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		objInfo.UserDefined = CleanMinioInternalMetadataKeys(objInfo.UserDefined) | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 		if _, err = DecryptObjectInfo(&objInfo, r.Header); err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-05-29 06:13:15 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-04-08 16:39:20 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 	// Validate pre-conditions if any.
 | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 	if checkPreconditions(ctx, w, r, objInfo) { | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-09-01 04:10:12 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 	// Set encryption response headers
 | 
					
						
							|  |  |  |  | 	if objectAPI.IsEncryptionSupported() { | 
					
						
							| 
									
										
										
										
											2018-09-24 01:24:10 +08:00
										 |  |  |  | 		if crypto.IsEncrypted(objInfo.UserDefined) { | 
					
						
							|  |  |  |  | 			switch { | 
					
						
							|  |  |  |  | 			case crypto.S3.IsEncrypted(objInfo.UserDefined): | 
					
						
							|  |  |  |  | 				w.Header().Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256) | 
					
						
							|  |  |  |  | 			case crypto.SSEC.IsEncrypted(objInfo.UserDefined): | 
					
						
							|  |  |  |  | 				w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm)) | 
					
						
							|  |  |  |  | 				w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5)) | 
					
						
							|  |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-07-11 08:12:22 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-11-08 07:18:59 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-13 03:07:43 +08:00
										 |  |  |  | 	if err = setObjectHeaders(w, objInfo, rs); err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2018-09-01 04:10:12 +08:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 	setHeadGetRespHeaders(w, r.URL.Query()) | 
					
						
							| 
									
										
										
										
											2018-08-09 06:39:47 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 	statusCodeWritten := false | 
					
						
							|  |  |  |  | 	httpWriter := ioutil.WriteOnClose(w) | 
					
						
							|  |  |  |  | 	if rs != nil { | 
					
						
							| 
									
										
										
										
											2018-08-09 06:39:47 +08:00
										 |  |  |  | 		statusCodeWritten = true | 
					
						
							|  |  |  |  | 		w.WriteHeader(http.StatusPartialContent) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 	// Write object content to response body
 | 
					
						
							|  |  |  |  | 	if _, err = io.Copy(httpWriter, gr); err != nil { | 
					
						
							| 
									
										
										
										
											2018-08-09 06:39:47 +08:00
										 |  |  |  | 		if !httpWriter.HasWritten() && !statusCodeWritten { // write error response only if no data or headers has been written to client yet
 | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-07-11 08:12:22 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-05-29 06:13:15 +08:00
										 |  |  |  | 		return | 
					
						
							| 
									
										
											  
											
												fs: Break fs package to top-level and introduce ObjectAPI interface.
ObjectAPI interface brings in changes needed for XL ObjectAPI layer.
The new interface for any ObjectAPI layer is as below
```
// ObjectAPI interface.
type ObjectAPI interface {
        // Bucket resource API.
        DeleteBucket(bucket string) *probe.Error
        ListBuckets() ([]BucketInfo, *probe.Error)
        MakeBucket(bucket string) *probe.Error
        GetBucketInfo(bucket string) (BucketInfo, *probe.Error)
        // Bucket query API.
        ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error)
        ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error)
        // Object resource API.
        GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error)
        GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error)
        PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error)
        DeleteObject(bucket, object string) *probe.Error
        // Object query API.
        NewMultipartUpload(bucket, object string) (string, *probe.Error)
        PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error)
        ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error)
        CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error)
        AbortMultipartUpload(bucket, object, uploadID string) *probe.Error
}
```
											
										 
											2016-03-31 07:15:28 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-08 07:18:59 +08:00
										 |  |  |  | 	if err = httpWriter.Close(); err != nil { | 
					
						
							| 
									
										
										
										
											2018-08-09 06:39:47 +08:00
										 |  |  |  | 		if !httpWriter.HasWritten() && !statusCodeWritten { // write error response only if no data or headers has been written to client yet
 | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-11-08 07:18:59 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-07-11 08:12:22 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-03-22 01:32:17 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// Notify object accessed via a GET request.
 | 
					
						
							| 
									
										
										
										
											2018-03-16 04:03:41 +08:00
										 |  |  |  | 	sendEvent(eventArgs{ | 
					
						
							| 
									
										
										
										
											2018-08-24 05:40:54 +08:00
										 |  |  |  | 		EventName:    event.ObjectAccessedGet, | 
					
						
							|  |  |  |  | 		BucketName:   bucket, | 
					
						
							|  |  |  |  | 		Object:       objInfo, | 
					
						
							|  |  |  |  | 		ReqParams:    extractReqParams(r), | 
					
						
							|  |  |  |  | 		RespElements: extractRespElements(w), | 
					
						
							|  |  |  |  | 		UserAgent:    r.UserAgent(), | 
					
						
							| 
									
										
										
										
											2019-03-26 02:45:42 +08:00
										 |  |  |  | 		Host:         handlers.GetSourceIP(r), | 
					
						
							| 
									
										
										
										
											2017-03-22 01:32:17 +08:00
										 |  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2015-02-16 09:03:27 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-07-01 11:15:48 +08:00
										 |  |  |  | // HeadObjectHandler - HEAD Object
 | 
					
						
							| 
									
										
										
										
											2015-02-24 08:46:48 +08:00
										 |  |  |  | // -----------
 | 
					
						
							|  |  |  |  | // The HEAD operation retrieves metadata from an object without returning the object itself.
 | 
					
						
							| 
									
										
										
										
											2016-04-13 03:45:15 +08:00
										 |  |  |  | func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2018-07-21 09:46:32 +08:00
										 |  |  |  | 	ctx := newContext(r, w, "HeadObject") | 
					
						
							| 
									
										
										
										
											2018-03-15 03:01:47 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-22 12:03:24 +08:00
										 |  |  |  | 	defer logger.AuditLog(w, r, "HeadObject", mustGetClaimsFromToken(r)) | 
					
						
							| 
									
										
										
										
											2018-10-13 03:25:59 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 	objectAPI := api.ObjectAPI() | 
					
						
							|  |  |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 		writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrServerNotInitialized)) | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 	if crypto.S3.IsRequested(r.Header) || crypto.S3KMS.IsRequested(r.Header) { // If SSE-S3 or SSE-KMS present -> AWS fails with undefined error
 | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 		writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrBadRequest)) | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if !api.EncryptionEnabled() && hasServerSideEncryptionHeader(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-12-15 13:39:59 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 	vars := mux.Vars(r) | 
					
						
							|  |  |  |  | 	bucket := vars["bucket"] | 
					
						
							|  |  |  |  | 	object := vars["object"] | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-13 03:43:44 +08:00
										 |  |  |  | 	if vid := r.URL.Query().Get("versionId"); vid != "" && vid != "null" { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 		writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrNoSuchVersion)) | 
					
						
							| 
									
										
										
										
											2018-12-13 03:43:44 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-22 04:48:58 +08:00
										 |  |  |  | 	getObjectInfo := objectAPI.GetObjectInfo | 
					
						
							| 
									
										
										
										
											2018-03-29 05:14:06 +08:00
										 |  |  |  | 	if api.CacheAPI() != nil { | 
					
						
							| 
									
										
										
										
											2018-09-22 04:48:58 +08:00
										 |  |  |  | 		getObjectInfo = api.CacheAPI().GetObjectInfo | 
					
						
							| 
									
										
										
										
											2018-03-29 05:14:06 +08:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 	opts, err := getOpts(ctx, r, bucket, object) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 		writeErrorResponseHeadersOnly(w, toAPIError(ctx, err)) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-02 14:43:27 +08:00
										 |  |  |  | 	if s3Error := checkRequestAuthType(ctx, r, policy.GetObjectAction, bucket, object); s3Error != ErrNone { | 
					
						
							|  |  |  |  | 		if getRequestAuthType(r) == authTypeAnonymous { | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 			// As per "Permission" section in
 | 
					
						
							|  |  |  |  | 			// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html
 | 
					
						
							|  |  |  |  | 			// If the object you request does not exist,
 | 
					
						
							|  |  |  |  | 			// the error Amazon S3 returns depends on
 | 
					
						
							|  |  |  |  | 			// whether you also have the s3:ListBucket
 | 
					
						
							|  |  |  |  | 			// permission.
 | 
					
						
							|  |  |  |  | 			// * If you have the s3:ListBucket permission
 | 
					
						
							|  |  |  |  | 			//   on the bucket, Amazon S3 will return an
 | 
					
						
							|  |  |  |  | 			//   HTTP status code 404 ("no such key")
 | 
					
						
							|  |  |  |  | 			//   error.
 | 
					
						
							|  |  |  |  | 			// * if you don’t have the s3:ListBucket
 | 
					
						
							|  |  |  |  | 			//   permission, Amazon S3 will return an HTTP
 | 
					
						
							|  |  |  |  | 			//   status code 403 ("access denied") error.`
 | 
					
						
							| 
									
										
										
										
											2018-05-02 14:43:27 +08:00
										 |  |  |  | 			if globalPolicySys.IsAllowed(policy.Args{ | 
					
						
							| 
									
										
										
										
											2018-04-25 06:53:30 +08:00
										 |  |  |  | 				Action:          policy.ListBucketAction, | 
					
						
							|  |  |  |  | 				BucketName:      bucket, | 
					
						
							| 
									
										
										
										
											2019-01-21 12:57:14 +08:00
										 |  |  |  | 				ConditionValues: getConditionValues(r, "", ""), | 
					
						
							| 
									
										
										
										
											2018-04-25 06:53:30 +08:00
										 |  |  |  | 				IsOwner:         false, | 
					
						
							|  |  |  |  | 			}) { | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 				_, err = getObjectInfo(ctx, bucket, object, opts) | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 				if toAPIError(ctx, err).Code == "NoSuchKey" { | 
					
						
							| 
									
										
										
										
											2018-05-02 14:43:27 +08:00
										 |  |  |  | 					s3Error = ErrNoSuchKey | 
					
						
							|  |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-04-25 06:53:30 +08:00
										 |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2015-09-19 18:20:07 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 		writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(s3Error)) | 
					
						
							| 
									
										
										
										
											2018-05-02 14:43:27 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-04-25 06:53:30 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-24 01:24:10 +08:00
										 |  |  |  | 	// Get request range.
 | 
					
						
							|  |  |  |  | 	var rs *HTTPRangeSpec | 
					
						
							|  |  |  |  | 	rangeHeader := r.Header.Get("Range") | 
					
						
							|  |  |  |  | 	if rangeHeader != "" { | 
					
						
							|  |  |  |  | 		if rs, err = parseRequestRangeSpec(rangeHeader); err != nil { | 
					
						
							|  |  |  |  | 			// Handle only errInvalidRange. Ignore other
 | 
					
						
							|  |  |  |  | 			// parse error and treat it as regular Get
 | 
					
						
							|  |  |  |  | 			// request like Amazon S3.
 | 
					
						
							|  |  |  |  | 			if err == errInvalidRange { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 				writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrInvalidRange)) | 
					
						
							| 
									
										
										
										
											2018-09-24 01:24:10 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 			logger.LogIf(ctx, err) | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-22 04:48:58 +08:00
										 |  |  |  | 	objInfo, err := getObjectInfo(ctx, bucket, object, opts) | 
					
						
							| 
									
										
										
										
											2018-05-02 14:43:27 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 		writeErrorResponseHeadersOnly(w, toAPIError(ctx, err)) | 
					
						
							| 
									
										
										
										
											2015-08-04 07:17:21 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 	if objectAPI.IsEncryptionSupported() { | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 		if _, err = DecryptObjectInfo(&objInfo, r.Header); err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 			writeErrorResponseHeadersOnly(w, toAPIError(ctx, err)) | 
					
						
							| 
									
										
										
										
											2017-11-08 07:18:59 +08:00
										 |  |  |  | 			return | 
					
						
							| 
									
										
										
										
											2018-09-24 01:24:10 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		objInfo.UserDefined = CleanMinioInternalMetadataKeys(objInfo.UserDefined) | 
					
						
							| 
									
										
										
										
											2018-09-24 01:24:10 +08:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// Set encryption response headers
 | 
					
						
							|  |  |  |  | 	if objectAPI.IsEncryptionSupported() { | 
					
						
							|  |  |  |  | 		if crypto.IsEncrypted(objInfo.UserDefined) { | 
					
						
							|  |  |  |  | 			switch { | 
					
						
							|  |  |  |  | 			case crypto.S3.IsEncrypted(objInfo.UserDefined): | 
					
						
							| 
									
										
										
										
											2018-08-18 03:52:14 +08:00
										 |  |  |  | 				w.Header().Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256) | 
					
						
							| 
									
										
										
										
											2018-09-24 01:24:10 +08:00
										 |  |  |  | 			case crypto.SSEC.IsEncrypted(objInfo.UserDefined): | 
					
						
							| 
									
										
										
										
											2018-10-17 03:24:27 +08:00
										 |  |  |  | 				// Validate the SSE-C Key set in the header.
 | 
					
						
							|  |  |  |  | 				if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 					writeErrorResponseHeadersOnly(w, toAPIError(ctx, err)) | 
					
						
							| 
									
										
										
										
											2018-10-17 03:24:27 +08:00
										 |  |  |  | 					return | 
					
						
							|  |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-08-18 03:52:14 +08:00
										 |  |  |  | 				w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm)) | 
					
						
							|  |  |  |  | 				w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5)) | 
					
						
							|  |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-11-08 07:18:59 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-02-29 10:10:37 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-11 08:12:22 +08:00
										 |  |  |  | 	// Validate pre-conditions if any.
 | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 	if checkPreconditions(ctx, w, r, objInfo) { | 
					
						
							| 
									
										
										
										
											2016-02-29 10:10:37 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-11 08:12:22 +08:00
										 |  |  |  | 	// Set standard object headers.
 | 
					
						
							| 
									
										
										
										
											2018-11-13 03:07:43 +08:00
										 |  |  |  | 	if err = setObjectHeaders(w, objInfo, rs); err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 		writeErrorResponseHeadersOnly(w, toAPIError(ctx, err)) | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-02-29 10:10:37 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-09 02:04:04 +08:00
										 |  |  |  | 	// Set any additional requested response headers.
 | 
					
						
							|  |  |  |  | 	setHeadGetRespHeaders(w, r.URL.Query()) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-28 02:57:08 +08:00
										 |  |  |  | 	// Successful response.
 | 
					
						
							| 
									
										
										
										
											2018-09-24 01:24:10 +08:00
										 |  |  |  | 	if rs != nil { | 
					
						
							|  |  |  |  | 		w.WriteHeader(http.StatusPartialContent) | 
					
						
							|  |  |  |  | 	} else { | 
					
						
							|  |  |  |  | 		w.WriteHeader(http.StatusOK) | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-03-22 01:32:17 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// Notify object accessed via a HEAD request.
 | 
					
						
							| 
									
										
										
										
											2018-03-16 04:03:41 +08:00
										 |  |  |  | 	sendEvent(eventArgs{ | 
					
						
							| 
									
										
										
										
											2018-08-24 05:40:54 +08:00
										 |  |  |  | 		EventName:    event.ObjectAccessedHead, | 
					
						
							|  |  |  |  | 		BucketName:   bucket, | 
					
						
							|  |  |  |  | 		Object:       objInfo, | 
					
						
							|  |  |  |  | 		ReqParams:    extractReqParams(r), | 
					
						
							|  |  |  |  | 		RespElements: extractRespElements(w), | 
					
						
							|  |  |  |  | 		UserAgent:    r.UserAgent(), | 
					
						
							| 
									
										
										
										
											2019-03-26 02:45:42 +08:00
										 |  |  |  | 		Host:         handlers.GetSourceIP(r), | 
					
						
							| 
									
										
										
										
											2017-03-22 01:32:17 +08:00
										 |  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2015-02-16 09:03:27 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-27 08:29:26 +08:00
										 |  |  |  | // Extract metadata relevant for an CopyObject operation based on conditional
 | 
					
						
							|  |  |  |  | // header values specified in X-Amz-Metadata-Directive.
 | 
					
						
							| 
									
										
										
										
											2018-07-11 11:27:10 +08:00
										 |  |  |  | func getCpObjMetadataFromHeader(ctx context.Context, r *http.Request, userMeta map[string]string) (map[string]string, error) { | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 	// Make a copy of the supplied metadata to avoid
 | 
					
						
							|  |  |  |  | 	// to change the original one.
 | 
					
						
							|  |  |  |  | 	defaultMeta := make(map[string]string, len(userMeta)) | 
					
						
							|  |  |  |  | 	for k, v := range userMeta { | 
					
						
							|  |  |  |  | 		defaultMeta[k] = v | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	// remove SSE Headers from source info
 | 
					
						
							|  |  |  |  | 	crypto.RemoveSSEHeaders(defaultMeta) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-27 08:29:26 +08:00
										 |  |  |  | 	// if x-amz-metadata-directive says REPLACE then
 | 
					
						
							|  |  |  |  | 	// we extract metadata from the input headers.
 | 
					
						
							| 
									
										
										
										
											2018-07-11 11:27:10 +08:00
										 |  |  |  | 	if isMetadataReplace(r.Header) { | 
					
						
							|  |  |  |  | 		return extractMetadata(ctx, r) | 
					
						
							| 
									
										
										
										
											2016-12-27 08:29:26 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-27 08:29:26 +08:00
										 |  |  |  | 	// if x-amz-metadata-directive says COPY then we
 | 
					
						
							|  |  |  |  | 	// return the default metadata.
 | 
					
						
							| 
									
										
										
										
											2018-07-11 11:27:10 +08:00
										 |  |  |  | 	if isMetadataCopy(r.Header) { | 
					
						
							| 
									
										
										
										
											2017-07-06 07:56:10 +08:00
										 |  |  |  | 		return defaultMeta, nil | 
					
						
							| 
									
										
										
										
											2016-12-27 08:29:26 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-27 08:29:26 +08:00
										 |  |  |  | 	// Copy is default behavior if not x-amz-metadata-directive is set.
 | 
					
						
							| 
									
										
										
										
											2017-07-06 07:56:10 +08:00
										 |  |  |  | 	return defaultMeta, nil | 
					
						
							| 
									
										
										
										
											2016-12-27 08:29:26 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-30 09:35:11 +08:00
										 |  |  |  | // Returns a minio-go Client configured to access remote host described by destDNSRecord
 | 
					
						
							|  |  |  |  | // Applicable only in a federated deployment
 | 
					
						
							| 
									
										
										
										
											2018-12-19 21:13:47 +08:00
										 |  |  |  | var getRemoteInstanceClient = func(r *http.Request, host string) (*miniogo.Core, error) { | 
					
						
							| 
									
										
										
										
											2018-11-30 09:35:11 +08:00
										 |  |  |  | 	cred := getReqAccessCred(r, globalServerConfig.GetRegion()) | 
					
						
							| 
									
										
										
										
											2018-12-19 21:13:47 +08:00
										 |  |  |  | 	// In a federated deployment, all the instances share config files
 | 
					
						
							|  |  |  |  | 	// and hence expected to have same credentials.
 | 
					
						
							|  |  |  |  | 	core, err := miniogo.NewCore(host, cred.AccessKey, cred.SecretKey, globalIsSSL) | 
					
						
							|  |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		return nil, err | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	core.SetCustomTransport(NewCustomHTTPTransport()) | 
					
						
							|  |  |  |  | 	return core, nil | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // Check if the bucket is on a remote site, this code only gets executed when federation is enabled.
 | 
					
						
							|  |  |  |  | var isRemoteCallRequired = func(ctx context.Context, bucket string, objAPI ObjectLayer) bool { | 
					
						
							|  |  |  |  | 	if globalDNSConfig == nil { | 
					
						
							|  |  |  |  | 		return false | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	_, err := objAPI.GetBucketInfo(ctx, bucket) | 
					
						
							|  |  |  |  | 	return err == toObjectErr(errVolumeNotFound, bucket) | 
					
						
							| 
									
										
										
										
											2018-11-30 09:35:11 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-27 19:04:52 +08:00
										 |  |  |  | // CopyObjectHandler - Copy Object
 | 
					
						
							|  |  |  |  | // ----------
 | 
					
						
							|  |  |  |  | // This implementation of the PUT operation adds an object to a bucket
 | 
					
						
							|  |  |  |  | // while reading the object from another source.
 | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | // Notice: The S3 client can send secret keys in headers for encryption related jobs,
 | 
					
						
							|  |  |  |  | // the handler should ensure to remove these keys before sending them to the object layer.
 | 
					
						
							|  |  |  |  | // Currently these keys are:
 | 
					
						
							|  |  |  |  | //   - X-Amz-Server-Side-Encryption-Customer-Key
 | 
					
						
							|  |  |  |  | //   - X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key
 | 
					
						
							| 
									
										
										
										
											2016-04-13 03:45:15 +08:00
										 |  |  |  | func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2018-07-21 09:46:32 +08:00
										 |  |  |  | 	ctx := newContext(r, w, "CopyObject") | 
					
						
							| 
									
										
										
										
											2018-03-15 03:01:47 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-22 12:03:24 +08:00
										 |  |  |  | 	defer logger.AuditLog(w, r, "CopyObject", mustGetClaimsFromToken(r)) | 
					
						
							| 
									
										
										
										
											2018-10-13 03:25:59 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 	objectAPI := api.ObjectAPI() | 
					
						
							|  |  |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-10-10 06:04:53 +08:00
										 |  |  |  | 	if crypto.S3KMS.IsRequested(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported
 | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if !api.EncryptionEnabled() && (hasServerSideEncryptionHeader(r.Header) || crypto.SSECopy.IsRequested(r.Header)) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-12-15 13:39:59 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 	vars := mux.Vars(r) | 
					
						
							|  |  |  |  | 	dstBucket := vars["bucket"] | 
					
						
							|  |  |  |  | 	dstObject := vars["object"] | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 06:53:30 +08:00
										 |  |  |  | 	if s3Error := checkRequestAuthType(ctx, r, policy.PutObjectAction, dstBucket, dstObject); s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-02-27 19:04:52 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-07 05:00:29 +08:00
										 |  |  |  | 	// TODO: Reject requests where body/payload is present, for now we don't even read it.
 | 
					
						
							| 
									
										
										
										
											2016-02-27 19:04:52 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-11 05:10:10 +08:00
										 |  |  |  | 	// Read escaped copy source path to check for parameters.
 | 
					
						
							|  |  |  |  | 	cpSrcPath := r.Header.Get("X-Amz-Copy-Source") | 
					
						
							| 
									
										
										
										
											2016-02-27 19:04:52 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-13 03:43:44 +08:00
										 |  |  |  | 	// Check https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectVersioning.html
 | 
					
						
							|  |  |  |  | 	// Regardless of whether you have enabled versioning, each object in your bucket
 | 
					
						
							|  |  |  |  | 	// has a version ID. If you have not enabled versioning, Amazon S3 sets the value
 | 
					
						
							|  |  |  |  | 	// of the version ID to null. If you have enabled versioning, Amazon S3 assigns a
 | 
					
						
							|  |  |  |  | 	// unique version ID value for the object.
 | 
					
						
							|  |  |  |  | 	if u, err := url.Parse(cpSrcPath); err == nil { | 
					
						
							|  |  |  |  | 		// Check if versionId query param was added, if yes then check if
 | 
					
						
							|  |  |  |  | 		// its non "null" value, we should error out since we do not support
 | 
					
						
							|  |  |  |  | 		// any versions other than "null".
 | 
					
						
							|  |  |  |  | 		if vid := u.Query().Get("versionId"); vid != "" && vid != "null" { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNoSuchVersion), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-12-13 03:43:44 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-01-11 05:10:10 +08:00
										 |  |  |  | 		// Note that url.Parse does the unescaping
 | 
					
						
							| 
									
										
										
										
											2018-12-13 03:43:44 +08:00
										 |  |  |  | 		cpSrcPath = u.Path | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-11 05:10:10 +08:00
										 |  |  |  | 	if vid := r.Header.Get("X-Amz-Copy-Source-Version-Id"); vid != "" { | 
					
						
							|  |  |  |  | 		// Check if versionId header was added, if yes then check if
 | 
					
						
							|  |  |  |  | 		// its non "null" value, we should error out since we do not support
 | 
					
						
							|  |  |  |  | 		// any versions other than "null".
 | 
					
						
							|  |  |  |  | 		if vid != "null" { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNoSuchVersion), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2019-01-11 05:10:10 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-12-13 03:43:44 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-27 08:29:26 +08:00
										 |  |  |  | 	srcBucket, srcObject := path2BucketAndObject(cpSrcPath) | 
					
						
							|  |  |  |  | 	// If source object is empty or bucket is empty, reply back invalid copy source.
 | 
					
						
							|  |  |  |  | 	if srcObject == "" || srcBucket == "" { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopySource), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-02-27 19:04:52 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-27 07:12:44 +08:00
										 |  |  |  | 	if s3Error := checkRequestAuthType(ctx, r, policy.GetObjectAction, srcBucket, srcObject); s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-27 07:12:44 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-27 08:29:26 +08:00
										 |  |  |  | 	// Check if metadata directive is valid.
 | 
					
						
							|  |  |  |  | 	if !isMetadataDirectiveValid(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidMetadataDirective), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-02-27 19:04:52 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	// This request header needs to be set prior to setting ObjectOptions
 | 
					
						
							|  |  |  |  | 	if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) { | 
					
						
							|  |  |  |  | 		r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256) | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-02-27 19:04:52 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-11 00:42:43 +08:00
										 |  |  |  | 	var srcOpts, dstOpts ObjectOptions | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 	srcOpts, err := copySrcOpts(ctx, r, srcBucket, srcObject) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		logger.LogIf(ctx, err) | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	// convert copy src encryption options for GET calls
 | 
					
						
							|  |  |  |  | 	var getOpts = ObjectOptions{} | 
					
						
							|  |  |  |  | 	getSSE := encrypt.SSE(srcOpts.ServerSideEncryption) | 
					
						
							|  |  |  |  | 	if getSSE != srcOpts.ServerSideEncryption { | 
					
						
							|  |  |  |  | 		getOpts.ServerSideEncryption = getSSE | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 	dstOpts, err = copyDstOpts(ctx, r, dstBucket, dstObject, nil) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		logger.LogIf(ctx, err) | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-07-10 03:13:40 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-11 02:31:50 +08:00
										 |  |  |  | 	cpSrcDstSame := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject)) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// Deny if WORM is enabled. If operation is key rotation of SSE-S3 encrypted object
 | 
					
						
							|  |  |  |  | 	// allow the operation
 | 
					
						
							|  |  |  |  | 	if globalWORMEnabled && !(cpSrcDstSame && crypto.S3.IsRequested(r.Header)) { | 
					
						
							| 
									
										
										
										
											2018-09-11 00:42:43 +08:00
										 |  |  |  | 		if _, err = objectAPI.GetObjectInfo(ctx, dstBucket, dstObject, dstOpts); err == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-28 07:44:45 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 	getObjectNInfo := objectAPI.GetObjectNInfo | 
					
						
							|  |  |  |  | 	if api.CacheAPI() != nil { | 
					
						
							|  |  |  |  | 		getObjectNInfo = api.CacheAPI().GetObjectNInfo | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	var lock = noLock | 
					
						
							|  |  |  |  | 	if !cpSrcDstSame { | 
					
						
							|  |  |  |  | 		lock = readLock | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-03-07 04:38:41 +08:00
										 |  |  |  | 	checkCopyPrecondFn := func(o ObjectInfo, encETag string) bool { | 
					
						
							|  |  |  |  | 		return checkCopyObjectPreconditions(ctx, w, r, o, encETag) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	getOpts.CheckCopyPrecondFn = checkCopyPrecondFn | 
					
						
							|  |  |  |  | 	srcOpts.CheckCopyPrecondFn = checkCopyPrecondFn | 
					
						
							| 
									
										
										
										
											2018-10-25 23:50:06 +08:00
										 |  |  |  | 	var rs *HTTPRangeSpec | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	gr, err := getObjectNInfo(ctx, srcBucket, srcObject, rs, r.Header, lock, getOpts) | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-03-07 04:38:41 +08:00
										 |  |  |  | 		if isErrPreconditionFailed(err) { | 
					
						
							|  |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	defer gr.Close() | 
					
						
							|  |  |  |  | 	srcInfo := gr.ObjInfo | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-27 19:04:52 +08:00
										 |  |  |  | 	/// maximum Upload size for object in a single CopyObject operation.
 | 
					
						
							| 
									
										
										
										
											2018-02-21 16:48:47 +08:00
										 |  |  |  | 	if isMaxObjectSize(srcInfo.Size) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrEntityTooLarge), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-02-27 19:04:52 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-11 02:31:50 +08:00
										 |  |  |  | 	// Deny if WORM is enabled, and it is not a SSE-S3 -> SSE-S3 key rotation or if metadata replacement is requested.
 | 
					
						
							|  |  |  |  | 	if globalWORMEnabled && cpSrcDstSame && (!crypto.S3.IsEncrypted(srcInfo.UserDefined) || isMetadataReplace(r.Header)) { | 
					
						
							|  |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r)) | 
					
						
							|  |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 	// We have to copy metadata only if source and destination are same.
 | 
					
						
							|  |  |  |  | 	// this changes for encryption which can be observed below.
 | 
					
						
							|  |  |  |  | 	if cpSrcDstSame { | 
					
						
							|  |  |  |  | 		srcInfo.metadataOnly = true | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 	var reader io.Reader | 
					
						
							|  |  |  |  | 	var length = srcInfo.Size | 
					
						
							| 
									
										
										
										
											2018-10-24 02:46:20 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// Set the actual size to the decrypted size if encrypted.
 | 
					
						
							|  |  |  |  | 	actualSize := srcInfo.Size | 
					
						
							|  |  |  |  | 	if crypto.IsEncrypted(srcInfo.UserDefined) { | 
					
						
							|  |  |  |  | 		actualSize, err = srcInfo.DecryptedSize() | 
					
						
							|  |  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-24 02:46:20 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-11-28 05:23:32 +08:00
										 |  |  |  | 		length = actualSize | 
					
						
							| 
									
										
										
										
											2018-10-24 02:46:20 +08:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-19 21:13:47 +08:00
										 |  |  |  | 	// Check if the destination bucket is on a remote site, this code only gets executed
 | 
					
						
							|  |  |  |  | 	// when federation is enabled, ie when globalDNSConfig is non 'nil'.
 | 
					
						
							|  |  |  |  | 	//
 | 
					
						
							|  |  |  |  | 	// This function is similar to isRemoteCallRequired but specifically for COPY object API
 | 
					
						
							|  |  |  |  | 	// if destination and source are same we do not need to check for destnation bucket
 | 
					
						
							|  |  |  |  | 	// to exist locally.
 | 
					
						
							|  |  |  |  | 	var isRemoteCopyRequired = func(ctx context.Context, srcBucket, dstBucket string, objAPI ObjectLayer) bool { | 
					
						
							|  |  |  |  | 		if globalDNSConfig == nil { | 
					
						
							|  |  |  |  | 			return false | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		if srcBucket == dstBucket { | 
					
						
							|  |  |  |  | 			return false | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		_, err := objAPI.GetBucketInfo(ctx, dstBucket) | 
					
						
							|  |  |  |  | 		return err == toObjectErr(errVolumeNotFound, dstBucket) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-12 04:05:41 +08:00
										 |  |  |  | 	var compressMetadata map[string]string | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 	// No need to compress for remote etcd calls
 | 
					
						
							|  |  |  |  | 	// Pass the decompressed stream to such calls.
 | 
					
						
							| 
									
										
										
										
											2018-12-19 21:13:47 +08:00
										 |  |  |  | 	isCompressed := objectAPI.IsCompressionSupported() && isCompressible(r.Header, srcObject) && !isRemoteCopyRequired(ctx, srcBucket, dstBucket, objectAPI) | 
					
						
							| 
									
										
										
										
											2018-10-24 02:46:20 +08:00
										 |  |  |  | 	if isCompressed { | 
					
						
							| 
									
										
										
										
											2018-12-12 04:05:41 +08:00
										 |  |  |  | 		compressMetadata = make(map[string]string, 2) | 
					
						
							|  |  |  |  | 		// Preserving the compression metadata.
 | 
					
						
							|  |  |  |  | 		compressMetadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV1 | 
					
						
							|  |  |  |  | 		compressMetadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(actualSize, 10) | 
					
						
							| 
									
										
										
										
											2018-10-24 02:46:20 +08:00
										 |  |  |  | 		// Remove all source encrypted related metadata to
 | 
					
						
							|  |  |  |  | 		// avoid copying them in target object.
 | 
					
						
							|  |  |  |  | 		crypto.RemoveInternalEntries(srcInfo.UserDefined) | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-06 00:35:37 +08:00
										 |  |  |  | 		reader = newSnappyCompressReader(gr) | 
					
						
							|  |  |  |  | 		length = -1 | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 	} else { | 
					
						
							|  |  |  |  | 		// Remove the metadata for remote calls.
 | 
					
						
							|  |  |  |  | 		delete(srcInfo.UserDefined, ReservedMetadataPrefix+"compression") | 
					
						
							|  |  |  |  | 		delete(srcInfo.UserDefined, ReservedMetadataPrefix+"actual-size") | 
					
						
							|  |  |  |  | 		reader = gr | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-03-03 09:24:02 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-09 09:35:40 +08:00
										 |  |  |  | 	srcInfo.Reader, err = hash.NewReader(reader, length, "", "", actualSize, globalCLIContext.StrictS3Compat) | 
					
						
							| 
									
										
										
										
											2018-03-03 09:24:02 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-03 09:24:02 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-11-28 05:23:32 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 	rawReader := srcInfo.Reader | 
					
						
							| 
									
										
										
										
											2018-11-28 05:23:32 +08:00
										 |  |  |  | 	pReader := NewPutObjReader(srcInfo.Reader, nil, nil) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 	var encMetadata = make(map[string]string) | 
					
						
							| 
									
										
										
										
											2018-10-24 02:46:20 +08:00
										 |  |  |  | 	if objectAPI.IsEncryptionSupported() && !isCompressed { | 
					
						
							| 
									
										
										
										
											2018-10-20 01:41:13 +08:00
										 |  |  |  | 		// Encryption parameters not applicable for this object.
 | 
					
						
							|  |  |  |  | 		if !crypto.IsEncrypted(srcInfo.UserDefined) && crypto.SSECopy.IsRequested(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-20 01:41:13 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		// Encryption parameters not present for this object.
 | 
					
						
							|  |  |  |  | 		if crypto.SSEC.IsEncrypted(srcInfo.UserDefined) && !crypto.SSECopy.IsRequested(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidSSECustomerAlgorithm), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-20 01:41:13 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 		var oldKey, newKey, objEncKey []byte | 
					
						
							| 
									
										
										
										
											2018-08-18 03:52:14 +08:00
										 |  |  |  | 		sseCopyS3 := crypto.S3.IsEncrypted(srcInfo.UserDefined) | 
					
						
							| 
									
										
										
										
											2018-10-20 01:41:13 +08:00
										 |  |  |  | 		sseCopyC := crypto.SSEC.IsEncrypted(srcInfo.UserDefined) && crypto.SSECopy.IsRequested(r.Header) | 
					
						
							| 
									
										
										
										
											2018-08-18 03:52:14 +08:00
										 |  |  |  | 		sseC := crypto.SSEC.IsRequested(r.Header) | 
					
						
							|  |  |  |  | 		sseS3 := crypto.S3.IsRequested(r.Header) | 
					
						
							| 
									
										
										
										
											2018-10-16 02:07:36 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 		isSourceEncrypted := sseCopyC || sseCopyS3 | 
					
						
							|  |  |  |  | 		isTargetEncrypted := sseC || sseS3 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 		if sseC { | 
					
						
							|  |  |  |  | 			newKey, err = ParseSSECustomerRequest(r) | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-11-06 02:26:10 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 		// If src == dst and either
 | 
					
						
							|  |  |  |  | 		// - the object is encrypted using SSE-C and two different SSE-C keys are present
 | 
					
						
							|  |  |  |  | 		// - the object is encrypted using SSE-S3 and the SSE-S3 header is present
 | 
					
						
							|  |  |  |  | 		// than execute a key rotation.
 | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		var keyRotation bool | 
					
						
							| 
									
										
										
										
											2018-11-06 02:26:10 +08:00
										 |  |  |  | 		if cpSrcDstSame && ((sseCopyC && sseC) || (sseS3 && sseCopyS3)) { | 
					
						
							|  |  |  |  | 			if sseCopyC && sseC { | 
					
						
							|  |  |  |  | 				oldKey, err = ParseSSECopyCustomerRequest(r.Header, srcInfo.UserDefined) | 
					
						
							|  |  |  |  | 				if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 					writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-11-06 02:26:10 +08:00
										 |  |  |  | 					return | 
					
						
							|  |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-03-03 09:24:02 +08:00
										 |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-12-20 06:12:53 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 			for k, v := range srcInfo.UserDefined { | 
					
						
							| 
									
										
										
										
											2018-12-20 06:12:53 +08:00
										 |  |  |  | 				if hasPrefix(k, ReservedMetadataPrefix) { | 
					
						
							|  |  |  |  | 					encMetadata[k] = v | 
					
						
							|  |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-11-06 02:26:10 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 			// In case of SSE-S3 oldKey and newKey aren't used - the KMS manages the keys.
 | 
					
						
							| 
									
										
										
										
											2018-07-10 08:18:28 +08:00
										 |  |  |  | 			if err = rotateKey(oldKey, newKey, srcBucket, srcObject, encMetadata); err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-03-13 04:52:38 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 			// Since we are rotating the keys, make sure to update the metadata.
 | 
					
						
							|  |  |  |  | 			srcInfo.metadataOnly = true | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 			keyRotation = true | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2018-10-16 02:07:36 +08:00
										 |  |  |  | 			if isSourceEncrypted || isTargetEncrypted { | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 				// We are not only copying just metadata instead
 | 
					
						
							|  |  |  |  | 				// we are creating a new object at this point, even
 | 
					
						
							|  |  |  |  | 				// if source and destination are same objects.
 | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 				if !keyRotation { | 
					
						
							|  |  |  |  | 					srcInfo.metadataOnly = false | 
					
						
							|  |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-10-16 02:07:36 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 			// Calculate the size of the target object
 | 
					
						
							|  |  |  |  | 			var targetSize int64 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 			switch { | 
					
						
							|  |  |  |  | 			case !isSourceEncrypted && !isTargetEncrypted: | 
					
						
							| 
									
										
										
										
											2018-11-28 05:23:32 +08:00
										 |  |  |  | 				targetSize = srcInfo.Size | 
					
						
							| 
									
										
										
										
											2018-10-16 02:07:36 +08:00
										 |  |  |  | 			case isSourceEncrypted && isTargetEncrypted: | 
					
						
							| 
									
										
										
										
											2019-01-10 07:17:21 +08:00
										 |  |  |  | 				objInfo := ObjectInfo{Size: actualSize} | 
					
						
							|  |  |  |  | 				targetSize = objInfo.EncryptedSize() | 
					
						
							| 
									
										
										
										
											2018-10-16 02:07:36 +08:00
										 |  |  |  | 			case !isSourceEncrypted && isTargetEncrypted: | 
					
						
							|  |  |  |  | 				targetSize = srcInfo.EncryptedSize() | 
					
						
							|  |  |  |  | 			case isSourceEncrypted && !isTargetEncrypted: | 
					
						
							|  |  |  |  | 				targetSize, _ = srcInfo.DecryptedSize() | 
					
						
							|  |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-11-28 05:23:32 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-16 02:07:36 +08:00
										 |  |  |  | 			if isTargetEncrypted { | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 				reader, objEncKey, err = newEncryptReader(srcInfo.Reader, newKey, dstBucket, dstObject, encMetadata, sseS3) | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 				if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 					writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 					return | 
					
						
							|  |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-03-03 09:24:02 +08:00
										 |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-08-18 03:52:14 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-16 02:07:36 +08:00
										 |  |  |  | 			if isSourceEncrypted { | 
					
						
							|  |  |  |  | 				// Remove all source encrypted related metadata to
 | 
					
						
							|  |  |  |  | 				// avoid copying them in target object.
 | 
					
						
							| 
									
										
										
										
											2018-10-20 01:50:52 +08:00
										 |  |  |  | 				crypto.RemoveInternalEntries(srcInfo.UserDefined) | 
					
						
							| 
									
										
										
										
											2018-10-16 02:07:36 +08:00
										 |  |  |  | 			} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-09 09:35:40 +08:00
										 |  |  |  | 			// do not try to verify encrypted content
 | 
					
						
							|  |  |  |  | 			srcInfo.Reader, err = hash.NewReader(reader, targetSize, "", "", targetSize, globalCLIContext.StrictS3Compat) | 
					
						
							| 
									
										
										
										
											2018-03-03 09:24:02 +08:00
										 |  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-03 09:24:02 +08:00
										 |  |  |  | 				return | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-11-28 05:23:32 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 			pReader = NewPutObjReader(rawReader, srcInfo.Reader, objEncKey) | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 	srcInfo.PutObjReader = pReader | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-11 11:27:10 +08:00
										 |  |  |  | 	srcInfo.UserDefined, err = getCpObjMetadataFromHeader(ctx, r, srcInfo.UserDefined) | 
					
						
							| 
									
										
										
										
											2017-07-06 07:56:10 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-10-04 01:38:25 +08:00
										 |  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2017-07-06 07:56:10 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-10-04 01:38:25 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-12 04:05:41 +08:00
										 |  |  |  | 	// Store the preserved compression metadata.
 | 
					
						
							|  |  |  |  | 	for k, v := range compressMetadata { | 
					
						
							|  |  |  |  | 		srcInfo.UserDefined[k] = v | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 	// We need to preserve the encryption headers set in EncryptRequest,
 | 
					
						
							|  |  |  |  | 	// so we do not want to override them, copy them instead.
 | 
					
						
							|  |  |  |  | 	for k, v := range encMetadata { | 
					
						
							|  |  |  |  | 		srcInfo.UserDefined[k] = v | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 	// Ensure that metadata does not contain sensitive information
 | 
					
						
							|  |  |  |  | 	crypto.RemoveSensitiveEntries(srcInfo.UserDefined) | 
					
						
							| 
									
										
										
										
											2016-12-27 08:29:26 +08:00
										 |  |  |  | 	// Check if x-amz-metadata-directive was not set to REPLACE and source,
 | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 	// desination are same objects. Apply this restriction also when
 | 
					
						
							|  |  |  |  | 	// metadataOnly is true indicating that we are not overwriting the object.
 | 
					
						
							| 
									
										
										
										
											2018-03-07 08:04:48 +08:00
										 |  |  |  | 	// if encryption is enabled we do not need explicit "REPLACE" metadata to
 | 
					
						
							|  |  |  |  | 	// be enabled as well - this is to allow for key-rotation.
 | 
					
						
							| 
									
										
										
										
											2018-11-06 02:26:10 +08:00
										 |  |  |  | 	if !isMetadataReplace(r.Header) && srcInfo.metadataOnly && !crypto.IsEncrypted(srcInfo.UserDefined) { | 
					
						
							| 
									
										
										
										
											2016-12-27 08:29:26 +08:00
										 |  |  |  | 		// If x-amz-metadata-directive is not set to REPLACE then we need
 | 
					
						
							|  |  |  |  | 		// to error out if source and destination are same.
 | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopyDest), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-12-27 08:29:26 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-12-11 08:15:12 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-12 03:02:30 +08:00
										 |  |  |  | 	var objInfo ObjectInfo | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-19 21:13:47 +08:00
										 |  |  |  | 	if isRemoteCopyRequired(ctx, srcBucket, dstBucket, objectAPI) { | 
					
						
							|  |  |  |  | 		var dstRecords []dns.SrvRecord | 
					
						
							|  |  |  |  | 		dstRecords, err = globalDNSConfig.Get(dstBucket) | 
					
						
							|  |  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-05-16 09:20:22 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-12-19 21:13:47 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 		// Send PutObject request to appropriate instance (in federated deployment)
 | 
					
						
							|  |  |  |  | 		client, rerr := getRemoteInstanceClient(r, getHostFromSrv(dstRecords)) | 
					
						
							|  |  |  |  | 		if rerr != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, rerr), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-12-19 21:13:47 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		remoteObjInfo, rerr := client.PutObject(dstBucket, dstObject, srcInfo.Reader, | 
					
						
							|  |  |  |  | 			srcInfo.Size, "", "", srcInfo.UserDefined, dstOpts.ServerSideEncryption) | 
					
						
							|  |  |  |  | 		if rerr != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, rerr), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-12-19 21:13:47 +08:00
										 |  |  |  | 			return | 
					
						
							| 
									
										
										
										
											2018-05-12 03:02:30 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-12-19 21:13:47 +08:00
										 |  |  |  | 		objInfo.ETag = remoteObjInfo.ETag | 
					
						
							|  |  |  |  | 		objInfo.ModTime = remoteObjInfo.LastModified | 
					
						
							| 
									
										
										
										
											2018-05-12 03:02:30 +08:00
										 |  |  |  | 	} else { | 
					
						
							|  |  |  |  | 		// Copy source object to destination, if source and destination
 | 
					
						
							|  |  |  |  | 		// object is same then only metadata is updated.
 | 
					
						
							| 
									
										
										
										
											2018-09-11 00:42:43 +08:00
										 |  |  |  | 		objInfo, err = objectAPI.CopyObject(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo, srcOpts, dstOpts) | 
					
						
							| 
									
										
										
										
											2018-05-12 03:02:30 +08:00
										 |  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-05-12 03:02:30 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-02-27 19:04:52 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-04-17 03:48:41 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 	response := generateCopyObjectResponse(getDecryptedETag(r.Header, objInfo, false), objInfo.ModTime) | 
					
						
							| 
									
										
										
										
											2016-03-07 04:16:22 +08:00
										 |  |  |  | 	encodedSuccessResponse := encodeResponse(response) | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// Write success response.
 | 
					
						
							|  |  |  |  | 	writeSuccessResponseXML(w, encodedSuccessResponse) | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-24 02:46:20 +08:00
										 |  |  |  | 	if objInfo.IsCompressed() { | 
					
						
							|  |  |  |  | 		objInfo.Size = actualSize | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-29 13:46:19 +08:00
										 |  |  |  | 	// Notify object created event.
 | 
					
						
							| 
									
										
										
										
											2018-03-16 04:03:41 +08:00
										 |  |  |  | 	sendEvent(eventArgs{ | 
					
						
							| 
									
										
										
										
											2018-11-03 09:40:08 +08:00
										 |  |  |  | 		EventName:    event.ObjectCreatedCopy, | 
					
						
							|  |  |  |  | 		BucketName:   dstBucket, | 
					
						
							|  |  |  |  | 		Object:       objInfo, | 
					
						
							|  |  |  |  | 		ReqParams:    extractReqParams(r), | 
					
						
							|  |  |  |  | 		RespElements: extractRespElements(w), | 
					
						
							|  |  |  |  | 		UserAgent:    r.UserAgent(), | 
					
						
							| 
									
										
										
										
											2019-03-26 02:45:42 +08:00
										 |  |  |  | 		Host:         handlers.GetSourceIP(r), | 
					
						
							| 
									
										
										
										
											2016-09-29 13:46:19 +08:00
										 |  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2016-02-27 19:04:52 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-07-01 11:15:48 +08:00
										 |  |  |  | // PutObjectHandler - PUT Object
 | 
					
						
							| 
									
										
										
										
											2015-02-24 08:46:48 +08:00
										 |  |  |  | // ----------
 | 
					
						
							|  |  |  |  | // This implementation of the PUT operation adds an object to a bucket.
 | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | // Notice: The S3 client can send secret keys in headers for encryption related jobs,
 | 
					
						
							|  |  |  |  | // the handler should ensure to remove these keys before sending them to the object layer.
 | 
					
						
							|  |  |  |  | // Currently these keys are:
 | 
					
						
							|  |  |  |  | //   - X-Amz-Server-Side-Encryption-Customer-Key
 | 
					
						
							|  |  |  |  | //   - X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key
 | 
					
						
							| 
									
										
										
										
											2016-04-13 03:45:15 +08:00
										 |  |  |  | func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2018-07-21 09:46:32 +08:00
										 |  |  |  | 	ctx := newContext(r, w, "PutObject") | 
					
						
							| 
									
										
										
										
											2018-03-15 03:01:47 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-22 12:03:24 +08:00
										 |  |  |  | 	defer logger.AuditLog(w, r, "PutObject", mustGetClaimsFromToken(r)) | 
					
						
							| 
									
										
										
										
											2018-10-13 03:25:59 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 	objectAPI := api.ObjectAPI() | 
					
						
							|  |  |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-10-10 06:04:53 +08:00
										 |  |  |  | 	if crypto.S3KMS.IsRequested(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported
 | 
					
						
							| 
									
										
										
										
											2016-02-27 19:04:52 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if !api.EncryptionEnabled() && hasServerSideEncryptionHeader(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-12-15 13:39:59 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-02-16 09:42:39 +08:00
										 |  |  |  | 	vars := mux.Vars(r) | 
					
						
							| 
									
										
										
										
											2016-02-27 19:04:52 +08:00
										 |  |  |  | 	bucket := vars["bucket"] | 
					
						
							|  |  |  |  | 	object := vars["object"] | 
					
						
							| 
									
										
										
										
											2015-04-23 10:29:39 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 	// X-Amz-Copy-Source shouldn't be set for this call.
 | 
					
						
							|  |  |  |  | 	if _, ok := r.Header["X-Amz-Copy-Source"]; ok { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopySource), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-27 12:36:16 +08:00
										 |  |  |  | 	// Validate storage class metadata if present
 | 
					
						
							|  |  |  |  | 	if _, ok := r.Header[amzStorageClassCanonical]; ok { | 
					
						
							|  |  |  |  | 		if !isValidStorageClassMeta(r.Header.Get(amzStorageClassCanonical)) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidStorageClass), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-12-27 12:36:16 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-12 17:08:55 +08:00
										 |  |  |  | 	// Get Content-Md5 sent by client and verify if valid
 | 
					
						
							| 
									
										
										
										
											2018-03-17 02:22:34 +08:00
										 |  |  |  | 	md5Bytes, err := checkValidMD5(r.Header) | 
					
						
							| 
									
										
										
										
											2016-03-13 08:08:15 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidDigest), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2015-04-23 07:28:13 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-12-28 15:00:36 +08:00
										 |  |  |  | 	/// if Content-Length is unknown/missing, deny the request
 | 
					
						
							| 
									
										
										
										
											2016-02-16 09:42:39 +08:00
										 |  |  |  | 	size := r.ContentLength | 
					
						
							| 
									
										
										
										
											2016-08-09 11:56:29 +08:00
										 |  |  |  | 	rAuthType := getRequestAuthType(r) | 
					
						
							|  |  |  |  | 	if rAuthType == authTypeStreamingSigned { | 
					
						
							| 
									
										
										
										
											2018-03-17 02:22:34 +08:00
										 |  |  |  | 		if sizeStr, ok := r.Header["X-Amz-Decoded-Content-Length"]; ok { | 
					
						
							|  |  |  |  | 			if sizeStr[0] == "" { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-17 02:22:34 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 			size, err = strconv.ParseInt(sizeStr[0], 10, 64) | 
					
						
							|  |  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-17 02:22:34 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-08-09 11:56:29 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-01-21 08:33:01 +08:00
										 |  |  |  | 	if size == -1 { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2015-04-29 17:19:51 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-10-01 05:32:13 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-30 01:51:59 +08:00
										 |  |  |  | 	/// maximum Upload size for objects in a single operation
 | 
					
						
							| 
									
										
										
										
											2015-04-29 17:19:51 +08:00
										 |  |  |  | 	if isMaxObjectSize(size) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrEntityTooLarge), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2015-04-29 17:19:51 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-07-03 11:31:22 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-11 11:27:10 +08:00
										 |  |  |  | 	metadata, err := extractMetadata(ctx, r) | 
					
						
							| 
									
										
										
										
											2017-07-06 07:56:10 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-07-06 07:56:10 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-07-11 11:27:10 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-21 04:07:03 +08:00
										 |  |  |  | 	if rAuthType == authTypeStreamingSigned { | 
					
						
							| 
									
										
										
										
											2017-03-28 08:02:04 +08:00
										 |  |  |  | 		if contentEncoding, ok := metadata["content-encoding"]; ok { | 
					
						
							|  |  |  |  | 			contentEncoding = trimAwsChunkedContentEncoding(contentEncoding) | 
					
						
							|  |  |  |  | 			if contentEncoding != "" { | 
					
						
							|  |  |  |  | 				// Make sure to trim and save the content-encoding
 | 
					
						
							|  |  |  |  | 				// parameter for a streaming signature which is set
 | 
					
						
							|  |  |  |  | 				// to a custom value for example: "aws-chunked,gzip".
 | 
					
						
							|  |  |  |  | 				metadata["content-encoding"] = contentEncoding | 
					
						
							|  |  |  |  | 			} else { | 
					
						
							|  |  |  |  | 				// Trimmed content encoding is empty when the header
 | 
					
						
							|  |  |  |  | 				// value is set to "aws-chunked" only.
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 				// Make sure to delete the content-encoding parameter
 | 
					
						
							|  |  |  |  | 				// for a streaming signature which is set to value
 | 
					
						
							|  |  |  |  | 				// for example: "aws-chunked"
 | 
					
						
							|  |  |  |  | 				delete(metadata, "content-encoding") | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-02-21 04:07:03 +08:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-22 13:30:34 +08:00
										 |  |  |  | 	var ( | 
					
						
							|  |  |  |  | 		md5hex    = hex.EncodeToString(md5Bytes) | 
					
						
							|  |  |  |  | 		sha256hex = "" | 
					
						
							| 
									
										
										
										
											2017-11-08 07:18:59 +08:00
										 |  |  |  | 		reader    io.Reader | 
					
						
							|  |  |  |  | 		s3Err     APIErrorCode | 
					
						
							| 
									
										
										
										
											2018-03-29 05:14:06 +08:00
										 |  |  |  | 		putObject = objectAPI.PutObject | 
					
						
							| 
									
										
										
										
											2017-10-22 13:30:34 +08:00
										 |  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2017-11-08 07:18:59 +08:00
										 |  |  |  | 	reader = r.Body | 
					
						
							| 
									
										
										
										
											2018-10-10 05:00:01 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// Check if put is allowed
 | 
					
						
							|  |  |  |  | 	if s3Err = isPutAllowed(rAuthType, bucket, object, r); s3Err != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-10 05:00:01 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-09 11:56:29 +08:00
										 |  |  |  | 	switch rAuthType { | 
					
						
							|  |  |  |  | 	case authTypeStreamingSigned: | 
					
						
							|  |  |  |  | 		// Initialize stream signature verifier.
 | 
					
						
							| 
									
										
										
										
											2017-11-08 07:18:59 +08:00
										 |  |  |  | 		reader, s3Err = newSignV4ChunkedReader(r) | 
					
						
							|  |  |  |  | 		if s3Err != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-08-09 11:56:29 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-10-01 05:32:13 +08:00
										 |  |  |  | 	case authTypeSignedV2, authTypePresignedV2: | 
					
						
							| 
									
										
										
										
											2017-11-08 07:18:59 +08:00
										 |  |  |  | 		s3Err = isReqAuthenticatedV2(r) | 
					
						
							|  |  |  |  | 		if s3Err != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-10-01 05:32:13 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-01-09 15:19:50 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-07 18:04:18 +08:00
										 |  |  |  | 	case authTypePresigned, authTypeSigned: | 
					
						
							| 
									
										
										
										
											2019-02-28 09:46:55 +08:00
										 |  |  |  | 		if s3Err = reqSignatureV4Verify(r, globalServerConfig.GetRegion(), serviceS3); s3Err != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-10-03 06:51:49 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		if !skipContentSha256Cksum(r) { | 
					
						
							| 
									
										
										
										
											2019-02-28 09:46:55 +08:00
										 |  |  |  | 			sha256hex = getContentSha256Cksum(r, serviceS3) | 
					
						
							| 
									
										
										
										
											2016-10-03 06:51:49 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-02-17 10:50:36 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-10-22 13:30:34 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-02 23:28:18 +08:00
										 |  |  |  | 	// This request header needs to be set prior to setting ObjectOptions
 | 
					
						
							|  |  |  |  | 	if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) { | 
					
						
							|  |  |  |  | 		r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 	actualSize := size | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) && size > 0 { | 
					
						
							|  |  |  |  | 		// Storing the compression metadata.
 | 
					
						
							|  |  |  |  | 		metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV1 | 
					
						
							|  |  |  |  | 		metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(size, 10) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-09 09:35:40 +08:00
										 |  |  |  | 		actualReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat) | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-09-28 15:44:59 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 		// Set compression metrics.
 | 
					
						
							| 
									
										
										
										
											2019-03-06 00:35:37 +08:00
										 |  |  |  | 		reader = newSnappyCompressReader(actualReader) | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 		size = -1   // Since compressed size is un-predictable.
 | 
					
						
							|  |  |  |  | 		md5hex = "" // Do not try to verify the content.
 | 
					
						
							|  |  |  |  | 		sha256hex = "" | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-09 09:35:40 +08:00
										 |  |  |  | 	hashReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat) | 
					
						
							| 
									
										
										
										
											2017-10-22 13:30:34 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-10-22 13:30:34 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	rawReader := hashReader | 
					
						
							|  |  |  |  | 	pReader := NewPutObjReader(rawReader, nil, nil) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	// get gateway encryption options
 | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 	var opts ObjectOptions | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 	opts, err = putOpts(ctx, r, bucket, object, metadata) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 		writeErrorResponseHeadersOnly(w, toAPIError(ctx, err)) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 07:44:45 +08:00
										 |  |  |  | 	// Deny if WORM is enabled
 | 
					
						
							|  |  |  |  | 	if globalWORMEnabled { | 
					
						
							| 
									
										
										
										
											2018-09-11 00:42:43 +08:00
										 |  |  |  | 		if _, err = objectAPI.GetObjectInfo(ctx, bucket, object, opts); err == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-28 07:44:45 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 	var objectEncryptionKey []byte | 
					
						
							| 
									
										
										
										
											2018-02-10 07:19:30 +08:00
										 |  |  |  | 	if objectAPI.IsEncryptionSupported() { | 
					
						
							| 
									
										
										
										
											2018-08-18 03:52:14 +08:00
										 |  |  |  | 		if hasServerSideEncryptionHeader(r.Header) && !hasSuffix(object, slashSeparator) { // handle SSE requests
 | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 			reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata) | 
					
						
							| 
									
										
										
										
											2018-02-10 07:19:30 +08:00
										 |  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-02-10 07:19:30 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 			info := ObjectInfo{Size: size} | 
					
						
							| 
									
										
										
										
											2019-05-09 09:35:40 +08:00
										 |  |  |  | 			// do not try to verify encrypted content
 | 
					
						
							|  |  |  |  | 			hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", size, globalCLIContext.StrictS3Compat) | 
					
						
							| 
									
										
										
										
											2018-02-10 07:19:30 +08:00
										 |  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-02-10 07:19:30 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 			pReader = NewPutObjReader(rawReader, hashReader, objectEncryptionKey) | 
					
						
							| 
									
										
										
										
											2017-11-08 07:18:59 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 	// Ensure that metadata does not contain sensitive information
 | 
					
						
							|  |  |  |  | 	crypto.RemoveSensitiveEntries(metadata) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-23 05:54:15 +08:00
										 |  |  |  | 	if api.CacheAPI() != nil && !hasServerSideEncryptionHeader(r.Header) { | 
					
						
							|  |  |  |  | 		putObject = api.CacheAPI().PutObject | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-29 05:14:06 +08:00
										 |  |  |  | 	// Create the object..
 | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 	objInfo, err := putObject(ctx, bucket, object, pReader, opts) | 
					
						
							| 
									
										
										
										
											2015-09-19 18:20:07 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2015-08-04 07:17:21 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-02-24 07:07:21 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-20 06:12:53 +08:00
										 |  |  |  | 	etag := objInfo.ETag | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 	if objInfo.IsCompressed() { | 
					
						
							| 
									
										
										
										
											2019-05-09 09:35:40 +08:00
										 |  |  |  | 		if !strings.HasSuffix(objInfo.ETag, "-1") { | 
					
						
							|  |  |  |  | 			etag = objInfo.ETag + "-1" | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-12-20 06:12:53 +08:00
										 |  |  |  | 	} else if hasServerSideEncryptionHeader(r.Header) { | 
					
						
							|  |  |  |  | 		etag = getDecryptedETag(r.Header, objInfo, false) | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-04-09 07:54:46 +08:00
										 |  |  |  | 	w.Header()["ETag"] = []string{"\"" + etag + "\""} | 
					
						
							| 
									
										
										
										
											2018-12-20 06:12:53 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-10 07:19:30 +08:00
										 |  |  |  | 	if objectAPI.IsEncryptionSupported() { | 
					
						
							| 
									
										
										
										
											2018-09-24 01:24:10 +08:00
										 |  |  |  | 		if crypto.IsEncrypted(objInfo.UserDefined) { | 
					
						
							| 
									
										
										
										
											2019-02-19 16:44:26 +08:00
										 |  |  |  | 			objInfo.Size, _ = objInfo.DecryptedSize() | 
					
						
							| 
									
										
										
										
											2018-09-24 01:24:10 +08:00
										 |  |  |  | 			switch { | 
					
						
							|  |  |  |  | 			case crypto.S3.IsEncrypted(objInfo.UserDefined): | 
					
						
							|  |  |  |  | 				w.Header().Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256) | 
					
						
							|  |  |  |  | 			case crypto.SSEC.IsRequested(r.Header): | 
					
						
							|  |  |  |  | 				w.Header().Set(crypto.SSECAlgorithm, r.Header.Get(crypto.SSECAlgorithm)) | 
					
						
							|  |  |  |  | 				w.Header().Set(crypto.SSECKeyMD5, r.Header.Get(crypto.SSECKeyMD5)) | 
					
						
							|  |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-02-10 07:19:30 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-11-08 07:18:59 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-02-10 07:19:30 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  |  | 	writeSuccessResponseHeadersOnly(w) | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-29 13:46:19 +08:00
										 |  |  |  | 	// Notify object created event.
 | 
					
						
							| 
									
										
										
										
											2018-03-16 04:03:41 +08:00
										 |  |  |  | 	sendEvent(eventArgs{ | 
					
						
							| 
									
										
										
										
											2018-11-03 09:40:08 +08:00
										 |  |  |  | 		EventName:    event.ObjectCreatedPut, | 
					
						
							|  |  |  |  | 		BucketName:   bucket, | 
					
						
							|  |  |  |  | 		Object:       objInfo, | 
					
						
							|  |  |  |  | 		ReqParams:    extractReqParams(r), | 
					
						
							|  |  |  |  | 		RespElements: extractRespElements(w), | 
					
						
							|  |  |  |  | 		UserAgent:    r.UserAgent(), | 
					
						
							| 
									
										
										
										
											2019-03-26 02:45:42 +08:00
										 |  |  |  | 		Host:         handlers.GetSourceIP(r), | 
					
						
							| 
									
										
										
										
											2016-09-29 13:46:19 +08:00
										 |  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2015-02-16 09:03:27 +08:00
										 |  |  |  | } | 
					
						
							| 
									
										
										
										
											2015-05-08 10:55:30 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-13 03:45:15 +08:00
										 |  |  |  | /// Multipart objectAPIHandlers
 | 
					
						
							| 
									
										
										
										
											2015-06-09 02:06:06 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-07 04:34:33 +08:00
										 |  |  |  | // NewMultipartUploadHandler - New multipart upload.
 | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | // Notice: The S3 client can send secret keys in headers for encryption related jobs,
 | 
					
						
							|  |  |  |  | // the handler should ensure to remove these keys before sending them to the object layer.
 | 
					
						
							|  |  |  |  | // Currently these keys are:
 | 
					
						
							|  |  |  |  | //   - X-Amz-Server-Side-Encryption-Customer-Key
 | 
					
						
							|  |  |  |  | //   - X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key
 | 
					
						
							| 
									
										
										
										
											2016-04-13 03:45:15 +08:00
										 |  |  |  | func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2018-07-21 09:46:32 +08:00
										 |  |  |  | 	ctx := newContext(r, w, "NewMultipartUpload") | 
					
						
							| 
									
										
										
										
											2018-03-15 03:01:47 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-22 12:03:24 +08:00
										 |  |  |  | 	defer logger.AuditLog(w, r, "NewMultipartUpload", mustGetClaimsFromToken(r)) | 
					
						
							| 
									
										
										
										
											2018-10-13 03:25:59 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 	objectAPI := api.ObjectAPI() | 
					
						
							|  |  |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-10-10 06:04:53 +08:00
										 |  |  |  | 	if crypto.S3KMS.IsRequested(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported
 | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if !api.EncryptionEnabled() && hasServerSideEncryptionHeader(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-12-15 13:39:59 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 	vars := mux.Vars(r) | 
					
						
							|  |  |  |  | 	bucket := vars["bucket"] | 
					
						
							|  |  |  |  | 	object := vars["object"] | 
					
						
							| 
									
										
										
										
											2018-04-06 06:04:40 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-17 04:12:06 +08:00
										 |  |  |  | 	if s3Error := checkRequestAuthType(ctx, r, policy.PutObjectAction, bucket, object); s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2019-01-17 04:12:06 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	// This request header needs to be set prior to setting ObjectOptions
 | 
					
						
							|  |  |  |  | 	if globalAutoEncryption && !crypto.SSEC.IsRequested(r.Header) { | 
					
						
							|  |  |  |  | 		r.Header.Add(crypto.SSEHeader, crypto.SSEAlgorithmAES256) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// get gateway encryption options
 | 
					
						
							|  |  |  |  | 	var opts ObjectOptions | 
					
						
							|  |  |  |  | 	var err error | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 	opts, err = putOpts(ctx, r, bucket, object, nil) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 		writeErrorResponseHeadersOnly(w, toAPIError(ctx, err)) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 07:44:45 +08:00
										 |  |  |  | 	// Deny if WORM is enabled
 | 
					
						
							|  |  |  |  | 	if globalWORMEnabled { | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 		if _, err = objectAPI.GetObjectInfo(ctx, bucket, object, opts); err == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-28 07:44:45 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-27 12:36:16 +08:00
										 |  |  |  | 	// Validate storage class metadata if present
 | 
					
						
							|  |  |  |  | 	if _, ok := r.Header[amzStorageClassCanonical]; ok { | 
					
						
							|  |  |  |  | 		if !isValidStorageClassMeta(r.Header.Get(amzStorageClassCanonical)) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidStorageClass), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-12-27 12:36:16 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 	var encMetadata = map[string]string{} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	if objectAPI.IsEncryptionSupported() { | 
					
						
							| 
									
										
										
										
											2018-08-18 03:52:14 +08:00
										 |  |  |  | 		if hasServerSideEncryptionHeader(r.Header) { | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 			if err = setEncryptionMetadata(r, bucket, object, encMetadata); err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 			// Set this for multipart only operations, we need to differentiate during
 | 
					
						
							|  |  |  |  | 			// decryption if the file was actually multipart or not.
 | 
					
						
							|  |  |  |  | 			encMetadata[ReservedMetadataPrefix+"Encrypted-Multipart"] = "" | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-11-08 07:18:59 +08:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-23 11:31:45 +08:00
										 |  |  |  | 	// Extract metadata that needs to be saved.
 | 
					
						
							| 
									
										
										
										
											2018-07-11 11:27:10 +08:00
										 |  |  |  | 	metadata, err := extractMetadata(ctx, r) | 
					
						
							| 
									
										
										
										
											2017-07-06 07:56:10 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-07-06 07:56:10 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-05-19 10:54:25 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 	// We need to preserve the encryption headers set in EncryptRequest,
 | 
					
						
							|  |  |  |  | 	// so we do not want to override them, copy them instead.
 | 
					
						
							|  |  |  |  | 	for k, v := range encMetadata { | 
					
						
							|  |  |  |  | 		metadata[k] = v | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 	// Ensure that metadata does not contain sensitive information
 | 
					
						
							|  |  |  |  | 	crypto.RemoveSensitiveEntries(metadata) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 	if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) { | 
					
						
							|  |  |  |  | 		// Storing the compression metadata.
 | 
					
						
							|  |  |  |  | 		metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV1 | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 	opts, err = putOpts(ctx, r, bucket, object, metadata) | 
					
						
							|  |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 		writeErrorResponseHeadersOnly(w, toAPIError(ctx, err)) | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-03-29 05:14:06 +08:00
										 |  |  |  | 	newMultipartUpload := objectAPI.NewMultipartUpload | 
					
						
							| 
									
										
										
										
											2019-05-23 05:54:15 +08:00
										 |  |  |  | 	if api.CacheAPI() != nil && !hasServerSideEncryptionHeader(r.Header) { | 
					
						
							|  |  |  |  | 		newMultipartUpload = api.CacheAPI().NewMultipartUpload | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 	uploadID, err := newMultipartUpload(ctx, bucket, object, opts) | 
					
						
							| 
									
										
										
										
											2015-09-19 18:20:07 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2015-08-04 07:17:21 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-09-19 18:20:07 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	response := generateInitiateMultipartUploadResponse(bucket, object, uploadID) | 
					
						
							| 
									
										
										
										
											2016-03-07 04:16:22 +08:00
										 |  |  |  | 	encodedSuccessResponse := encodeResponse(response) | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// Write success response.
 | 
					
						
							|  |  |  |  | 	writeSuccessResponseXML(w, encodedSuccessResponse) | 
					
						
							| 
									
										
										
										
											2015-05-08 10:55:30 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | // CopyObjectPartHandler - uploads a part by copying data from an existing object as data source.
 | 
					
						
							|  |  |  |  | func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2018-07-21 09:46:32 +08:00
										 |  |  |  | 	ctx := newContext(r, w, "CopyObjectPart") | 
					
						
							| 
									
										
										
										
											2018-03-15 03:01:47 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-22 12:03:24 +08:00
										 |  |  |  | 	defer logger.AuditLog(w, r, "CopyObjectPart", mustGetClaimsFromToken(r)) | 
					
						
							| 
									
										
										
										
											2018-10-13 03:25:59 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 	objectAPI := api.ObjectAPI() | 
					
						
							|  |  |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-10-10 06:04:53 +08:00
										 |  |  |  | 	if crypto.S3KMS.IsRequested(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported
 | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if !api.EncryptionEnabled() && (hasServerSideEncryptionHeader(r.Header) || crypto.SSECopy.IsRequested(r.Header)) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-12-15 13:39:59 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	vars := mux.Vars(r) | 
					
						
							|  |  |  |  | 	dstBucket := vars["bucket"] | 
					
						
							|  |  |  |  | 	dstObject := vars["object"] | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 06:53:30 +08:00
										 |  |  |  | 	if s3Error := checkRequestAuthType(ctx, r, policy.PutObjectAction, dstBucket, dstObject); s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-11 05:10:10 +08:00
										 |  |  |  | 	// Read escaped copy source path to check for parameters.
 | 
					
						
							|  |  |  |  | 	cpSrcPath := r.Header.Get("X-Amz-Copy-Source") | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-14 14:04:37 +08:00
										 |  |  |  | 	// Check https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectVersioning.html
 | 
					
						
							|  |  |  |  | 	// Regardless of whether you have enabled versioning, each object in your bucket
 | 
					
						
							|  |  |  |  | 	// has a version ID. If you have not enabled versioning, Amazon S3 sets the value
 | 
					
						
							|  |  |  |  | 	// of the version ID to null. If you have enabled versioning, Amazon S3 assigns a
 | 
					
						
							|  |  |  |  | 	// unique version ID value for the object.
 | 
					
						
							|  |  |  |  | 	if u, err := url.Parse(cpSrcPath); err == nil { | 
					
						
							|  |  |  |  | 		// Check if versionId query param was added, if yes then check if
 | 
					
						
							|  |  |  |  | 		// its non "null" value, we should error out since we do not support
 | 
					
						
							|  |  |  |  | 		// any versions other than "null".
 | 
					
						
							|  |  |  |  | 		if vid := u.Query().Get("versionId"); vid != "" && vid != "null" { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNoSuchVersion), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-12-14 14:04:37 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-01-11 05:10:10 +08:00
										 |  |  |  | 		// Note that url.Parse does the unescaping
 | 
					
						
							| 
									
										
										
										
											2018-12-14 14:04:37 +08:00
										 |  |  |  | 		cpSrcPath = u.Path | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-11 05:10:10 +08:00
										 |  |  |  | 	if vid := r.Header.Get("X-Amz-Copy-Source-Version-Id"); vid != "" { | 
					
						
							|  |  |  |  | 		// Check if X-Amz-Copy-Source-Version-Id header was added, if yes then check if
 | 
					
						
							|  |  |  |  | 		// its non "null" value, we should error out since we do not support
 | 
					
						
							|  |  |  |  | 		// any versions other than "null".
 | 
					
						
							|  |  |  |  | 		if vid != "null" { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNoSuchVersion), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2019-01-11 05:10:10 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-12-14 14:04:37 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 	srcBucket, srcObject := path2BucketAndObject(cpSrcPath) | 
					
						
							|  |  |  |  | 	// If source object is empty or bucket is empty, reply back invalid copy source.
 | 
					
						
							|  |  |  |  | 	if srcObject == "" || srcBucket == "" { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopySource), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-27 07:12:44 +08:00
										 |  |  |  | 	if s3Error := checkRequestAuthType(ctx, r, policy.GetObjectAction, srcBucket, srcObject); s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-27 07:12:44 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 	uploadID := r.URL.Query().Get("uploadId") | 
					
						
							|  |  |  |  | 	partIDString := r.URL.Query().Get("partNumber") | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	partID, err := strconv.Atoi(partIDString) | 
					
						
							|  |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidPart), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// check partID with maximum part ID for multipart objects
 | 
					
						
							|  |  |  |  | 	if isMaxPartID(partID) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidMaxParts), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-11 00:42:43 +08:00
										 |  |  |  | 	var srcOpts, dstOpts ObjectOptions | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 	srcOpts, err = copySrcOpts(ctx, r, srcBucket, srcObject) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	// convert copy src and dst encryption options for GET/PUT calls
 | 
					
						
							|  |  |  |  | 	var getOpts = ObjectOptions{} | 
					
						
							|  |  |  |  | 	if srcOpts.ServerSideEncryption != nil { | 
					
						
							|  |  |  |  | 		getOpts.ServerSideEncryption = encrypt.SSE(srcOpts.ServerSideEncryption) | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 	dstOpts, err = copyDstOpts(ctx, r, dstBucket, dstObject, nil) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 07:44:45 +08:00
										 |  |  |  | 	// Deny if WORM is enabled
 | 
					
						
							|  |  |  |  | 	if globalWORMEnabled { | 
					
						
							| 
									
										
										
										
											2018-09-11 00:42:43 +08:00
										 |  |  |  | 		if _, err = objectAPI.GetObjectInfo(ctx, dstBucket, dstObject, dstOpts); err == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-28 07:44:45 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 	getObjectNInfo := objectAPI.GetObjectNInfo | 
					
						
							|  |  |  |  | 	if api.CacheAPI() != nil { | 
					
						
							|  |  |  |  | 		getObjectNInfo = api.CacheAPI().GetObjectNInfo | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 	// Get request range.
 | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 	var rs *HTTPRangeSpec | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 	rangeHeader := r.Header.Get("x-amz-copy-source-range") | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 	if rangeHeader != "" { | 
					
						
							|  |  |  |  | 		var parseRangeErr error | 
					
						
							|  |  |  |  | 		if rs, parseRangeErr = parseCopyPartRangeSpec(rangeHeader); parseRangeErr != nil { | 
					
						
							|  |  |  |  | 			// Handle only errInvalidRange
 | 
					
						
							|  |  |  |  | 			// Ignore other parse error and treat it as regular Get request like Amazon S3.
 | 
					
						
							|  |  |  |  | 			logger.GetReqInfo(ctx).AppendTags("rangeHeader", rangeHeader) | 
					
						
							|  |  |  |  | 			logger.LogIf(ctx, parseRangeErr) | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 			writeCopyPartErr(ctx, w, parseRangeErr, r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-03-07 04:38:41 +08:00
										 |  |  |  | 	checkCopyPartPrecondFn := func(o ObjectInfo, encETag string) bool { | 
					
						
							|  |  |  |  | 		return checkCopyObjectPartPreconditions(ctx, w, r, o, encETag) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	getOpts.CheckCopyPrecondFn = checkCopyPartPrecondFn | 
					
						
							|  |  |  |  | 	srcOpts.CheckCopyPrecondFn = checkCopyPartPrecondFn | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	gr, err := getObjectNInfo(ctx, srcBucket, srcObject, rs, r.Header, readLock, getOpts) | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-03-07 04:38:41 +08:00
										 |  |  |  | 		if isErrPreconditionFailed(err) { | 
					
						
							|  |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	defer gr.Close() | 
					
						
							|  |  |  |  | 	srcInfo := gr.ObjInfo | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-23 05:23:23 +08:00
										 |  |  |  | 	actualPartSize := srcInfo.Size | 
					
						
							|  |  |  |  | 	if crypto.IsEncrypted(srcInfo.UserDefined) { | 
					
						
							|  |  |  |  | 		actualPartSize, err = srcInfo.DecryptedSize() | 
					
						
							|  |  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-23 05:23:23 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 	// Special care for CopyObjectPart
 | 
					
						
							| 
									
										
										
										
											2018-10-23 05:23:23 +08:00
										 |  |  |  | 	if partRangeErr := checkCopyPartRangeWithSize(rs, actualPartSize); partRangeErr != nil { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 		writeCopyPartErr(ctx, w, partRangeErr, r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-26 03:39:46 +08:00
										 |  |  |  | 	// Get the object offset & length
 | 
					
						
							| 
									
										
										
										
											2018-10-25 23:50:06 +08:00
										 |  |  |  | 	startOffset, length, err := rs.GetOffsetLength(actualPartSize) | 
					
						
							|  |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-25 23:50:06 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 	/// maximum copy size for multipart objects in a single operation
 | 
					
						
							| 
									
										
										
										
											2017-03-04 02:14:17 +08:00
										 |  |  |  | 	if isMaxAllowedPartSize(length) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrEntityTooLarge), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-25 23:50:06 +08:00
										 |  |  |  | 	actualPartSize = length | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 	var reader io.Reader | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-24 02:46:20 +08:00
										 |  |  |  | 	var li ListPartsInfo | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	li, err = objectAPI.ListObjectParts(ctx, dstBucket, dstObject, uploadID, 0, 1, dstOpts) | 
					
						
							| 
									
										
										
										
											2018-10-24 02:46:20 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-24 02:46:20 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-10-25 23:50:06 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-24 02:46:20 +08:00
										 |  |  |  | 	// Read compression metadata preserved in the init multipart for the decision.
 | 
					
						
							|  |  |  |  | 	_, compressPart := li.UserDefined[ReservedMetadataPrefix+"compression"] | 
					
						
							|  |  |  |  | 	isCompressed := compressPart | 
					
						
							|  |  |  |  | 	// Compress only if the compression is enabled during initial multipart.
 | 
					
						
							|  |  |  |  | 	if isCompressed { | 
					
						
							| 
									
										
										
										
											2019-03-06 00:35:37 +08:00
										 |  |  |  | 		reader = newSnappyCompressReader(gr) | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 		length = -1 | 
					
						
							|  |  |  |  | 	} else { | 
					
						
							|  |  |  |  | 		reader = gr | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-09 09:35:40 +08:00
										 |  |  |  | 	srcInfo.Reader, err = hash.NewReader(reader, length, "", "", actualPartSize, globalCLIContext.StrictS3Compat) | 
					
						
							| 
									
										
										
										
											2018-03-03 09:24:02 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-03 09:24:02 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 	rawReader := srcInfo.Reader | 
					
						
							|  |  |  |  | 	pReader := NewPutObjReader(rawReader, nil, nil) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	isEncrypted := false | 
					
						
							|  |  |  |  | 	var objectEncryptionKey []byte | 
					
						
							| 
									
										
										
										
											2018-10-24 02:46:20 +08:00
										 |  |  |  | 	if objectAPI.IsEncryptionSupported() && !isCompressed { | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		li, lerr := objectAPI.ListObjectParts(ctx, dstBucket, dstObject, uploadID, 0, 1, dstOpts) | 
					
						
							|  |  |  |  | 		if lerr != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, lerr), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		li.UserDefined = CleanMinioInternalMetadataKeys(li.UserDefined) | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 		dstOpts, err = copyDstOpts(ctx, r, dstBucket, dstObject, li.UserDefined) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-08-18 03:52:14 +08:00
										 |  |  |  | 		if crypto.IsEncrypted(li.UserDefined) { | 
					
						
							| 
									
										
										
										
											2018-10-25 23:50:06 +08:00
										 |  |  |  | 			if !crypto.SSEC.IsRequested(r.Header) && crypto.SSEC.IsEncrypted(li.UserDefined) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrSSEMultipartEncrypted), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-29 21:40:34 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 			if crypto.S3.IsEncrypted(li.UserDefined) && crypto.SSEC.IsRequested(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrSSEMultipartEncrypted), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-02-06 05:36:38 +08:00
										 |  |  |  | 			isEncrypted = true | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 			var key []byte | 
					
						
							| 
									
										
										
										
											2018-08-18 03:52:14 +08:00
										 |  |  |  | 			if crypto.SSEC.IsRequested(r.Header) { | 
					
						
							|  |  |  |  | 				key, err = ParseSSECustomerRequest(r) | 
					
						
							|  |  |  |  | 				if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 					writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-08-18 03:52:14 +08:00
										 |  |  |  | 					return | 
					
						
							|  |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-07-10 08:18:28 +08:00
										 |  |  |  | 			objectEncryptionKey, err = decryptObjectInfo(key, dstBucket, dstObject, li.UserDefined) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-13 00:29:56 +08:00
										 |  |  |  | 			var partIDbin [4]byte | 
					
						
							|  |  |  |  | 			binary.LittleEndian.PutUint32(partIDbin[:], uint32(partID)) // marshal part ID
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 			mac := hmac.New(sha256.New, objectEncryptionKey) // derive part encryption key from part ID and object key
 | 
					
						
							|  |  |  |  | 			mac.Write(partIDbin[:]) | 
					
						
							|  |  |  |  | 			partEncryptionKey := mac.Sum(nil) | 
					
						
							|  |  |  |  | 			reader, err = sio.EncryptReader(reader, sio.Config{Key: partEncryptionKey}) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-13 02:23:32 +08:00
										 |  |  |  | 			info := ObjectInfo{Size: length} | 
					
						
							| 
									
										
										
										
											2019-05-09 09:35:40 +08:00
										 |  |  |  | 			srcInfo.Reader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", length, globalCLIContext.StrictS3Compat) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 			pReader = NewPutObjReader(rawReader, srcInfo.Reader, objectEncryptionKey) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 	srcInfo.PutObjReader = pReader | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 	// Copy source object to destination, if source and destination
 | 
					
						
							|  |  |  |  | 	// object is same then only metadata is updated.
 | 
					
						
							| 
									
										
										
										
											2018-10-25 23:50:06 +08:00
										 |  |  |  | 	partInfo, err := objectAPI.CopyObjectPart(ctx, srcBucket, srcObject, dstBucket, dstObject, uploadID, partID, | 
					
						
							|  |  |  |  | 		startOffset, length, srcInfo, srcOpts, dstOpts) | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 	if isEncrypted { | 
					
						
							|  |  |  |  | 		partInfo.ETag = tryDecryptETag(objectEncryptionKey, partInfo.ETag, crypto.SSEC.IsRequested(r.Header)) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-01 01:38:34 +08:00
										 |  |  |  | 	response := generateCopyObjectPartResponse(partInfo.ETag, partInfo.LastModified) | 
					
						
							|  |  |  |  | 	encodedSuccessResponse := encodeResponse(response) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// Write success response.
 | 
					
						
							|  |  |  |  | 	writeSuccessResponseXML(w, encodedSuccessResponse) | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // PutObjectPartHandler - uploads an incoming part for an ongoing multipart operation.
 | 
					
						
							| 
									
										
										
										
											2016-04-13 03:45:15 +08:00
										 |  |  |  | func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2018-07-21 09:46:32 +08:00
										 |  |  |  | 	ctx := newContext(r, w, "PutObjectPart") | 
					
						
							| 
									
										
										
										
											2018-03-15 03:01:47 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-22 12:03:24 +08:00
										 |  |  |  | 	defer logger.AuditLog(w, r, "PutObjectPart", mustGetClaimsFromToken(r)) | 
					
						
							| 
									
										
										
										
											2018-10-13 03:25:59 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 	objectAPI := api.ObjectAPI() | 
					
						
							|  |  |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-10-10 06:04:53 +08:00
										 |  |  |  | 	if crypto.S3KMS.IsRequested(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) // SSE-KMS is not supported
 | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	if !api.EncryptionEnabled() && hasServerSideEncryptionHeader(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-12-15 13:39:59 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-08-18 12:07:19 +08:00
										 |  |  |  | 	vars := mux.Vars(r) | 
					
						
							|  |  |  |  | 	bucket := vars["bucket"] | 
					
						
							|  |  |  |  | 	object := vars["object"] | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-04 08:32:04 +08:00
										 |  |  |  | 	// X-Amz-Copy-Source shouldn't be set for this call.
 | 
					
						
							|  |  |  |  | 	if _, ok := r.Header["X-Amz-Copy-Source"]; ok { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopySource), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-03-04 08:32:04 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-06 08:43:48 +08:00
										 |  |  |  | 	// get Content-Md5 sent by client and verify if valid
 | 
					
						
							| 
									
										
										
										
											2018-03-17 02:22:34 +08:00
										 |  |  |  | 	md5Bytes, err := checkValidMD5(r.Header) | 
					
						
							| 
									
										
										
										
											2016-03-13 08:08:15 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidDigest), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2015-10-17 10:09:35 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-28 15:00:36 +08:00
										 |  |  |  | 	/// if Content-Length is unknown/missing, throw away
 | 
					
						
							| 
									
										
										
										
											2016-02-16 09:42:39 +08:00
										 |  |  |  | 	size := r.ContentLength | 
					
						
							| 
									
										
										
										
											2016-08-09 11:56:29 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	rAuthType := getRequestAuthType(r) | 
					
						
							|  |  |  |  | 	// For auth type streaming signature, we need to gather a different content length.
 | 
					
						
							|  |  |  |  | 	if rAuthType == authTypeStreamingSigned { | 
					
						
							| 
									
										
										
										
											2018-03-17 02:22:34 +08:00
										 |  |  |  | 		if sizeStr, ok := r.Header["X-Amz-Decoded-Content-Length"]; ok { | 
					
						
							|  |  |  |  | 			if sizeStr[0] == "" { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-17 02:22:34 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 			size, err = strconv.ParseInt(sizeStr[0], 10, 64) | 
					
						
							|  |  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-17 02:22:34 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-08-09 11:56:29 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-12-28 15:00:36 +08:00
										 |  |  |  | 	if size == -1 { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2015-12-28 15:00:36 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-08 13:43:19 +08:00
										 |  |  |  | 	/// maximum Upload size for multipart objects in a single operation
 | 
					
						
							| 
									
										
										
										
											2017-03-04 02:14:17 +08:00
										 |  |  |  | 	if isMaxAllowedPartSize(size) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrEntityTooLarge), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2015-05-08 10:55:30 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-05-01 07:29:03 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-16 09:42:39 +08:00
										 |  |  |  | 	uploadID := r.URL.Query().Get("uploadId") | 
					
						
							|  |  |  |  | 	partIDString := r.URL.Query().Get("partNumber") | 
					
						
							| 
									
										
										
										
											2015-05-10 10:39:00 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-30 05:24:10 +08:00
										 |  |  |  | 	partID, err := strconv.Atoi(partIDString) | 
					
						
							|  |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidPart), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-03-03 03:22:58 +08:00
										 |  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2015-05-08 10:55:30 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-07-03 11:31:22 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-24 16:52:47 +08:00
										 |  |  |  | 	// check partID with maximum part ID for multipart objects
 | 
					
						
							|  |  |  |  | 	if isMaxPartID(partID) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidMaxParts), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-05-24 16:52:47 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-22 13:30:34 +08:00
										 |  |  |  | 	var ( | 
					
						
							|  |  |  |  | 		md5hex    = hex.EncodeToString(md5Bytes) | 
					
						
							|  |  |  |  | 		sha256hex = "" | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 		reader    io.Reader | 
					
						
							| 
									
										
										
										
											2018-10-10 05:00:01 +08:00
										 |  |  |  | 		s3Error   APIErrorCode | 
					
						
							| 
									
										
										
										
											2017-10-22 13:30:34 +08:00
										 |  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 	reader = r.Body | 
					
						
							| 
									
										
										
										
											2018-10-10 05:00:01 +08:00
										 |  |  |  | 	if s3Error = isPutAllowed(rAuthType, bucket, object, r); s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-10 05:00:01 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-09 11:56:29 +08:00
										 |  |  |  | 	switch rAuthType { | 
					
						
							|  |  |  |  | 	case authTypeStreamingSigned: | 
					
						
							|  |  |  |  | 		// Initialize stream signature verifier.
 | 
					
						
							| 
									
										
										
										
											2017-10-22 13:30:34 +08:00
										 |  |  |  | 		reader, s3Error = newSignV4ChunkedReader(r) | 
					
						
							| 
									
										
										
										
											2016-08-09 11:56:29 +08:00
										 |  |  |  | 		if s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-08-09 11:56:29 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-10-01 05:32:13 +08:00
										 |  |  |  | 	case authTypeSignedV2, authTypePresignedV2: | 
					
						
							| 
									
										
										
										
											2018-10-10 05:00:01 +08:00
										 |  |  |  | 		if s3Error = isReqAuthenticatedV2(r); s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-10-01 05:32:13 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-04-07 18:04:18 +08:00
										 |  |  |  | 	case authTypePresigned, authTypeSigned: | 
					
						
							| 
									
										
										
										
											2019-02-28 09:46:55 +08:00
										 |  |  |  | 		if s3Error = reqSignatureV4Verify(r, globalServerConfig.GetRegion(), serviceS3); s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-10-03 06:51:49 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 		if !skipContentSha256Cksum(r) { | 
					
						
							| 
									
										
										
										
											2019-02-28 09:46:55 +08:00
										 |  |  |  | 			sha256hex = getContentSha256Cksum(r, serviceS3) | 
					
						
							| 
									
										
										
										
											2016-10-03 06:51:49 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-02-17 10:50:36 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-10-22 13:30:34 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 	actualSize := size | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	// get encryption options
 | 
					
						
							|  |  |  |  | 	var opts ObjectOptions | 
					
						
							|  |  |  |  | 	if crypto.SSEC.IsRequested(r.Header) { | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 		opts, err = putOpts(ctx, r, bucket, object, nil) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 	var li ListPartsInfo | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	li, err = objectAPI.ListObjectParts(ctx, bucket, object, uploadID, 0, 1, opts) | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	// Read compression metadata preserved in the init multipart for the decision.
 | 
					
						
							|  |  |  |  | 	_, compressPart := li.UserDefined[ReservedMetadataPrefix+"compression"] | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	isCompressed := false | 
					
						
							|  |  |  |  | 	if objectAPI.IsCompressionSupported() && compressPart { | 
					
						
							| 
									
										
										
										
											2019-05-09 09:35:40 +08:00
										 |  |  |  | 		actualReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat) | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-09-28 15:44:59 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 		// Set compression metrics.
 | 
					
						
							| 
									
										
										
										
											2019-03-06 00:35:37 +08:00
										 |  |  |  | 		reader = newSnappyCompressReader(actualReader) | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 		size = -1   // Since compressed size is un-predictable.
 | 
					
						
							|  |  |  |  | 		md5hex = "" // Do not try to verify the content.
 | 
					
						
							|  |  |  |  | 		sha256hex = "" | 
					
						
							|  |  |  |  | 		isCompressed = true | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-09 09:35:40 +08:00
										 |  |  |  | 	hashReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat) | 
					
						
							| 
									
										
										
										
											2017-10-22 13:30:34 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2017-10-22 13:30:34 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 	rawReader := hashReader | 
					
						
							|  |  |  |  | 	pReader := NewPutObjReader(rawReader, nil, nil) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 07:44:45 +08:00
										 |  |  |  | 	// Deny if WORM is enabled
 | 
					
						
							|  |  |  |  | 	if globalWORMEnabled { | 
					
						
							| 
									
										
										
										
											2018-09-11 00:42:43 +08:00
										 |  |  |  | 		if _, err = objectAPI.GetObjectInfo(ctx, bucket, object, opts); err == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-28 07:44:45 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-19 07:05:05 +08:00
										 |  |  |  | 	isEncrypted := false | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 	var objectEncryptionKey []byte | 
					
						
							| 
									
										
										
										
											2018-09-28 11:36:17 +08:00
										 |  |  |  | 	if objectAPI.IsEncryptionSupported() && !isCompressed { | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 		var li ListPartsInfo | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		li, err = objectAPI.ListObjectParts(ctx, bucket, object, uploadID, 0, 1, ObjectOptions{}) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		li.UserDefined = CleanMinioInternalMetadataKeys(li.UserDefined) | 
					
						
							| 
									
										
										
										
											2018-08-18 03:52:14 +08:00
										 |  |  |  | 		if crypto.IsEncrypted(li.UserDefined) { | 
					
						
							| 
									
										
										
										
											2018-10-19 07:05:05 +08:00
										 |  |  |  | 			if !crypto.SSEC.IsRequested(r.Header) && crypto.SSEC.IsEncrypted(li.UserDefined) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrSSEMultipartEncrypted), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 			isEncrypted = true // to detect SSE-S3 encryption
 | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  |  | 			opts, err = putOpts(ctx, r, bucket, object, li.UserDefined) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 			var key []byte | 
					
						
							| 
									
										
										
										
											2018-08-18 03:52:14 +08:00
										 |  |  |  | 			if crypto.SSEC.IsRequested(r.Header) { | 
					
						
							|  |  |  |  | 				key, err = ParseSSECustomerRequest(r) | 
					
						
							|  |  |  |  | 				if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 					writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-08-18 03:52:14 +08:00
										 |  |  |  | 					return | 
					
						
							|  |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 			} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 			// Calculating object encryption key
 | 
					
						
							| 
									
										
										
										
											2018-07-10 08:18:28 +08:00
										 |  |  |  | 			objectEncryptionKey, err = decryptObjectInfo(key, bucket, object, li.UserDefined) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 			var partIDbin [4]byte | 
					
						
							|  |  |  |  | 			binary.LittleEndian.PutUint32(partIDbin[:], uint32(partID)) // marshal part ID
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 			mac := hmac.New(sha256.New, objectEncryptionKey) // derive part encryption key from part ID and object key
 | 
					
						
							|  |  |  |  | 			mac.Write(partIDbin[:]) | 
					
						
							|  |  |  |  | 			partEncryptionKey := mac.Sum(nil) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-19 07:05:05 +08:00
										 |  |  |  | 			reader, err = sio.EncryptReader(hashReader, sio.Config{Key: partEncryptionKey}) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 			info := ObjectInfo{Size: size} | 
					
						
							| 
									
										
										
										
											2019-05-09 09:35:40 +08:00
										 |  |  |  | 			// do not try to verify encrypted content
 | 
					
						
							|  |  |  |  | 			hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", size, globalCLIContext.StrictS3Compat) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 			pReader = NewPutObjReader(rawReader, hashReader, objectEncryptionKey) | 
					
						
							| 
									
										
										
										
											2018-03-02 03:37:57 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-29 05:14:06 +08:00
										 |  |  |  | 	putObjectPart := objectAPI.PutObjectPart | 
					
						
							| 
									
										
										
										
											2019-05-23 05:54:15 +08:00
										 |  |  |  | 	if api.CacheAPI() != nil && !isEncrypted { | 
					
						
							|  |  |  |  | 		putObjectPart = api.CacheAPI().PutObjectPart | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 	partInfo, err := putObjectPart(ctx, bucket, object, uploadID, partID, pReader, opts) | 
					
						
							| 
									
										
										
										
											2015-09-19 18:20:07 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-03-13 08:08:15 +08:00
										 |  |  |  | 		// Verify if the underlying error is signature mismatch.
 | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2015-08-04 07:17:21 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-12-20 06:12:53 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	etag := partInfo.ETag | 
					
						
							| 
									
										
										
										
											2019-05-09 09:35:40 +08:00
										 |  |  |  | 	if isEncrypted { | 
					
						
							| 
									
										
										
										
											2018-12-20 06:12:53 +08:00
										 |  |  |  | 		etag = tryDecryptETag(objectEncryptionKey, partInfo.ETag, crypto.SSEC.IsRequested(r.Header)) | 
					
						
							| 
									
										
										
										
											2016-02-02 04:19:54 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-04-09 07:54:46 +08:00
										 |  |  |  | 	w.Header()["ETag"] = []string{"\"" + etag + "\""} | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	writeSuccessResponseHeadersOnly(w) | 
					
						
							| 
									
										
										
										
											2015-05-08 10:55:30 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-07-01 11:15:48 +08:00
										 |  |  |  | // AbortMultipartUploadHandler - Abort multipart upload
 | 
					
						
							| 
									
										
										
										
											2016-04-13 03:45:15 +08:00
										 |  |  |  | func (api objectAPIHandlers) AbortMultipartUploadHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2018-07-21 09:46:32 +08:00
										 |  |  |  | 	ctx := newContext(r, w, "AbortMultipartUpload") | 
					
						
							| 
									
										
										
										
											2018-03-15 03:01:47 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-22 12:03:24 +08:00
										 |  |  |  | 	defer logger.AuditLog(w, r, "AbortMultipartUpload", mustGetClaimsFromToken(r)) | 
					
						
							| 
									
										
										
										
											2018-10-13 03:25:59 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-16 09:42:39 +08:00
										 |  |  |  | 	vars := mux.Vars(r) | 
					
						
							| 
									
										
										
										
											2015-05-10 07:06:35 +08:00
										 |  |  |  | 	bucket := vars["bucket"] | 
					
						
							|  |  |  |  | 	object := vars["object"] | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 	objectAPI := api.ObjectAPI() | 
					
						
							|  |  |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-03-29 05:14:06 +08:00
										 |  |  |  | 	abortMultipartUpload := objectAPI.AbortMultipartUpload | 
					
						
							| 
									
										
										
										
											2019-05-23 05:54:15 +08:00
										 |  |  |  | 	if api.CacheAPI() != nil { | 
					
						
							|  |  |  |  | 		abortMultipartUpload = api.CacheAPI().AbortMultipartUpload | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-04-25 06:53:30 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	if s3Error := checkRequestAuthType(ctx, r, policy.AbortMultipartUploadAction, bucket, object); s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-03-13 08:08:15 +08:00
										 |  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2016-02-16 09:42:39 +08:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 07:44:45 +08:00
										 |  |  |  | 	// Deny if WORM is enabled
 | 
					
						
							|  |  |  |  | 	if globalWORMEnabled { | 
					
						
							| 
									
										
										
										
											2018-09-11 00:42:43 +08:00
										 |  |  |  | 		if _, err := objectAPI.GetObjectInfo(ctx, bucket, object, ObjectOptions{}); err == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-28 07:44:45 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-18 22:31:46 +08:00
										 |  |  |  | 	uploadID, _, _, _, s3Error := getObjectResources(r.URL.Query()) | 
					
						
							|  |  |  |  | 	if s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-18 22:31:46 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-03-29 05:14:06 +08:00
										 |  |  |  | 	if err := abortMultipartUpload(ctx, bucket, object, uploadID); err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2015-08-04 07:17:21 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-11-03 09:40:08 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-10-17 11:02:37 +08:00
										 |  |  |  | 	writeSuccessNoContent(w) | 
					
						
							| 
									
										
										
										
											2015-05-10 07:06:35 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-07-01 11:15:48 +08:00
										 |  |  |  | // ListObjectPartsHandler - List object parts
 | 
					
						
							| 
									
										
										
										
											2016-04-13 03:45:15 +08:00
										 |  |  |  | func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2018-07-21 09:46:32 +08:00
										 |  |  |  | 	ctx := newContext(r, w, "ListObjectParts") | 
					
						
							| 
									
										
										
										
											2018-03-15 03:01:47 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-22 12:03:24 +08:00
										 |  |  |  | 	defer logger.AuditLog(w, r, "ListObjectParts", mustGetClaimsFromToken(r)) | 
					
						
							| 
									
										
										
										
											2018-10-13 03:25:59 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-16 09:42:39 +08:00
										 |  |  |  | 	vars := mux.Vars(r) | 
					
						
							| 
									
										
										
										
											2015-10-17 10:09:35 +08:00
										 |  |  |  | 	bucket := vars["bucket"] | 
					
						
							|  |  |  |  | 	object := vars["object"] | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 	objectAPI := api.ObjectAPI() | 
					
						
							|  |  |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 06:53:30 +08:00
										 |  |  |  | 	if s3Error := checkRequestAuthType(ctx, r, policy.ListMultipartUploadPartsAction, bucket, object); s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-03-13 08:08:15 +08:00
										 |  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2016-02-16 09:42:39 +08:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-24 14:14:24 +08:00
										 |  |  |  | 	uploadID, partNumberMarker, maxParts, encodingType, s3Error := getObjectResources(r.URL.Query()) | 
					
						
							| 
									
										
										
										
											2018-10-18 22:31:46 +08:00
										 |  |  |  | 	if s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-18 22:31:46 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
											
												objectAPI: Fix object API interface, remove unnecessary structs.
ObjectAPI changes.
```
ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, *probe.Error)
ListMultipartUploads(bucket, objectPrefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, *probe.Error)
ListObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (ListPartsInfo, *probe.Error)
CompleteMultipartUpload(bucket string, object string, uploadID string, parts []completePart) (ObjectInfo, *probe.Error)
```
											
										 
											2016-04-03 16:34:20 +08:00
										 |  |  |  | 	if partNumberMarker < 0 { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidPartNumberMarker), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2015-07-17 08:22:45 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
											
												objectAPI: Fix object API interface, remove unnecessary structs.
ObjectAPI changes.
```
ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, *probe.Error)
ListMultipartUploads(bucket, objectPrefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, *probe.Error)
ListObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (ListPartsInfo, *probe.Error)
CompleteMultipartUpload(bucket string, object string, uploadID string, parts []completePart) (ObjectInfo, *probe.Error)
```
											
										 
											2016-04-03 16:34:20 +08:00
										 |  |  |  | 	if maxParts < 0 { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidMaxParts), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2015-07-17 08:22:45 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 	var opts ObjectOptions | 
					
						
							|  |  |  |  | 	listPartsInfo, err := objectAPI.ListObjectParts(ctx, bucket, object, uploadID, partNumberMarker, maxParts, opts) | 
					
						
							| 
									
										
										
										
											2015-09-19 18:20:07 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2015-08-04 07:17:21 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 	var ssec bool | 
					
						
							|  |  |  |  | 	if objectAPI.IsEncryptionSupported() { | 
					
						
							|  |  |  |  | 		var li ListPartsInfo | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		li, err = objectAPI.ListObjectParts(ctx, bucket, object, uploadID, 0, 1, opts) | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		if crypto.IsEncrypted(li.UserDefined) { | 
					
						
							|  |  |  |  | 			var key []byte | 
					
						
							|  |  |  |  | 			if crypto.SSEC.IsEncrypted(li.UserDefined) { | 
					
						
							|  |  |  |  | 				ssec = true | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 			var objectEncryptionKey []byte | 
					
						
							|  |  |  |  | 			if crypto.S3.IsEncrypted(li.UserDefined) { | 
					
						
							|  |  |  |  | 				// Calculating object encryption key
 | 
					
						
							|  |  |  |  | 				objectEncryptionKey, err = decryptObjectInfo(key, bucket, object, li.UserDefined) | 
					
						
							|  |  |  |  | 				if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 					writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 					return | 
					
						
							|  |  |  |  | 				} | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 			parts := make([]PartInfo, len(listPartsInfo.Parts)) | 
					
						
							|  |  |  |  | 			for i, p := range listPartsInfo.Parts { | 
					
						
							|  |  |  |  | 				part := p | 
					
						
							|  |  |  |  | 				part.ETag = tryDecryptETag(objectEncryptionKey, p.ETag, ssec) | 
					
						
							|  |  |  |  | 				parts[i] = part | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 			listPartsInfo.Parts = parts | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-24 14:14:24 +08:00
										 |  |  |  | 	response := generateListPartsResponse(listPartsInfo, encodingType) | 
					
						
							| 
									
										
										
										
											2016-03-07 04:16:22 +08:00
										 |  |  |  | 	encodedSuccessResponse := encodeResponse(response) | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
											
												accessPolicy: Implement Put, Get, Delete access policy.
This patch implements Get,Put,Delete bucket policies
Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
Currently supports following actions.
   "*":                             true,
   "s3:*":                          true,
   "s3:GetObject":                  true,
   "s3:ListBucket":                 true,
   "s3:PutObject":                  true,
   "s3:CreateBucket":               true,
   "s3:GetBucketLocation":          true,
   "s3:DeleteBucket":               true,
   "s3:DeleteObject":               true,
   "s3:AbortMultipartUpload":       true,
   "s3:ListBucketMultipartUploads": true,
   "s3:ListMultipartUploadParts":   true,
following conditions for "StringEquals" and "StringNotEquals"
   "s3:prefix", "s3:max-keys"
											
										 
											2016-02-04 08:46:56 +08:00
										 |  |  |  | 	// Write success response.
 | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  |  | 	writeSuccessResponseXML(w, encodedSuccessResponse) | 
					
						
							| 
									
										
										
										
											2015-05-10 02:41:26 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-21 14:20:15 +08:00
										 |  |  |  | type whiteSpaceWriter struct { | 
					
						
							|  |  |  |  | 	http.ResponseWriter | 
					
						
							|  |  |  |  | 	http.Flusher | 
					
						
							|  |  |  |  | 	written bool | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | func (w *whiteSpaceWriter) Write(b []byte) (n int, err error) { | 
					
						
							|  |  |  |  | 	n, err = w.ResponseWriter.Write(b) | 
					
						
							|  |  |  |  | 	w.written = true | 
					
						
							|  |  |  |  | 	return | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | func (w *whiteSpaceWriter) WriteHeader(statusCode int) { | 
					
						
							|  |  |  |  | 	if !w.written { | 
					
						
							|  |  |  |  | 		w.ResponseWriter.WriteHeader(statusCode) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // Send empty whitespaces every 10 seconds to the client till completeMultiPartUpload() is
 | 
					
						
							|  |  |  |  | // done so that the client does not time out. Downside is we might send 200 OK and
 | 
					
						
							|  |  |  |  | // then send error XML. But accoording to S3 spec the client is supposed to check
 | 
					
						
							|  |  |  |  | // for error XML even if it received 200 OK. But for erasure this is not a problem
 | 
					
						
							|  |  |  |  | // as completeMultiPartUpload() is quick. Even For FS, it would not be an issue as
 | 
					
						
							|  |  |  |  | // we do background append as and when the parts arrive and completeMultiPartUpload
 | 
					
						
							|  |  |  |  | // is quick. Only in a rare case where parts would be out of order will
 | 
					
						
							|  |  |  |  | // FS:completeMultiPartUpload() take a longer time.
 | 
					
						
							|  |  |  |  | func sendWhiteSpace(ctx context.Context, w http.ResponseWriter) <-chan bool { | 
					
						
							|  |  |  |  | 	doneCh := make(chan bool) | 
					
						
							| 
									
										
										
										
											2019-02-06 12:58:09 +08:00
										 |  |  |  | 	go func() { | 
					
						
							|  |  |  |  | 		ticker := time.NewTicker(time.Second * 10) | 
					
						
							| 
									
										
										
										
											2019-02-21 14:20:15 +08:00
										 |  |  |  | 		headerWritten := false | 
					
						
							| 
									
										
										
										
											2019-02-06 12:58:09 +08:00
										 |  |  |  | 		for { | 
					
						
							|  |  |  |  | 			select { | 
					
						
							|  |  |  |  | 			case <-ticker.C: | 
					
						
							| 
									
										
										
										
											2019-02-21 14:20:15 +08:00
										 |  |  |  | 				// Write header if not written yet.
 | 
					
						
							|  |  |  |  | 				if !headerWritten { | 
					
						
							|  |  |  |  | 					w.Write([]byte(xml.Header)) | 
					
						
							|  |  |  |  | 					headerWritten = true | 
					
						
							| 
									
										
										
										
											2019-02-06 12:58:09 +08:00
										 |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2019-02-21 14:20:15 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 				// Once header is written keep writing empty spaces
 | 
					
						
							|  |  |  |  | 				// which are ignored by client SDK XML parsers.
 | 
					
						
							|  |  |  |  | 				// This occurs when server takes long time to completeMultiPartUpload()
 | 
					
						
							|  |  |  |  | 				w.Write([]byte(" ")) | 
					
						
							| 
									
										
										
										
											2019-02-06 12:58:09 +08:00
										 |  |  |  | 				w.(http.Flusher).Flush() | 
					
						
							| 
									
										
										
										
											2019-02-21 14:20:15 +08:00
										 |  |  |  | 			case doneCh <- headerWritten: | 
					
						
							| 
									
										
										
										
											2019-02-06 12:58:09 +08:00
										 |  |  |  | 				ticker.Stop() | 
					
						
							|  |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	}() | 
					
						
							|  |  |  |  | 	return doneCh | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-22 11:08:08 +08:00
										 |  |  |  | // CompleteMultipartUploadHandler - Complete multipart upload.
 | 
					
						
							| 
									
										
										
										
											2016-04-13 03:45:15 +08:00
										 |  |  |  | func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2018-07-21 09:46:32 +08:00
										 |  |  |  | 	ctx := newContext(r, w, "CompleteMultipartUpload") | 
					
						
							| 
									
										
										
										
											2018-03-15 03:01:47 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-22 12:03:24 +08:00
										 |  |  |  | 	defer logger.AuditLog(w, r, "CompleteMultipartUpload", mustGetClaimsFromToken(r)) | 
					
						
							| 
									
										
										
										
											2018-10-13 03:25:59 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-16 09:42:39 +08:00
										 |  |  |  | 	vars := mux.Vars(r) | 
					
						
							| 
									
										
										
										
											2015-05-08 10:55:30 +08:00
										 |  |  |  | 	bucket := vars["bucket"] | 
					
						
							|  |  |  |  | 	object := vars["object"] | 
					
						
							| 
									
										
										
										
											2015-07-01 05:42:29 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 	objectAPI := api.ObjectAPI() | 
					
						
							|  |  |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 06:53:30 +08:00
										 |  |  |  | 	if s3Error := checkRequestAuthType(ctx, r, policy.PutObjectAction, bucket, object); s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-11-22 05:51:05 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 07:44:45 +08:00
										 |  |  |  | 	// Deny if WORM is enabled
 | 
					
						
							|  |  |  |  | 	if globalWORMEnabled { | 
					
						
							| 
									
										
										
										
											2018-09-11 00:42:43 +08:00
										 |  |  |  | 		if _, err := objectAPI.GetObjectInfo(ctx, bucket, object, ObjectOptions{}); err == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-28 07:44:45 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												fs: Break fs package to top-level and introduce ObjectAPI interface.
ObjectAPI interface brings in changes needed for XL ObjectAPI layer.
The new interface for any ObjectAPI layer is as below
```
// ObjectAPI interface.
type ObjectAPI interface {
        // Bucket resource API.
        DeleteBucket(bucket string) *probe.Error
        ListBuckets() ([]BucketInfo, *probe.Error)
        MakeBucket(bucket string) *probe.Error
        GetBucketInfo(bucket string) (BucketInfo, *probe.Error)
        // Bucket query API.
        ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error)
        ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error)
        // Object resource API.
        GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error)
        GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error)
        PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error)
        DeleteObject(bucket, object string) *probe.Error
        // Object query API.
        NewMultipartUpload(bucket, object string) (string, *probe.Error)
        PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error)
        ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error)
        CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error)
        AbortMultipartUpload(bucket, object, uploadID string) *probe.Error
}
```
											
										 
											2016-03-31 07:15:28 +08:00
										 |  |  |  | 	// Get upload id.
 | 
					
						
							| 
									
										
										
										
											2018-10-18 22:31:46 +08:00
										 |  |  |  | 	uploadID, _, _, _, s3Error := getObjectResources(r.URL.Query()) | 
					
						
							|  |  |  |  | 	if s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-18 22:31:46 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-03-03 03:22:58 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-08 07:18:59 +08:00
										 |  |  |  | 	completeMultipartBytes, err := goioutil.ReadAll(r.Body) | 
					
						
							| 
									
										
										
										
											2016-04-30 05:24:10 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
											  
											
												fs: Break fs package to top-level and introduce ObjectAPI interface.
ObjectAPI interface brings in changes needed for XL ObjectAPI layer.
The new interface for any ObjectAPI layer is as below
```
// ObjectAPI interface.
type ObjectAPI interface {
        // Bucket resource API.
        DeleteBucket(bucket string) *probe.Error
        ListBuckets() ([]BucketInfo, *probe.Error)
        MakeBucket(bucket string) *probe.Error
        GetBucketInfo(bucket string) (BucketInfo, *probe.Error)
        // Bucket query API.
        ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error)
        ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error)
        // Object resource API.
        GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error)
        GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error)
        PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error)
        DeleteObject(bucket, object string) *probe.Error
        // Object query API.
        NewMultipartUpload(bucket, object string) (string, *probe.Error)
        PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error)
        ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error)
        CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error)
        AbortMultipartUpload(bucket, object, uploadID string) *probe.Error
}
```
											
										 
											2016-03-31 07:15:28 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-11-14 16:25:10 +08:00
										 |  |  |  | 	complMultipartUpload := &CompleteMultipartUpload{} | 
					
						
							| 
									
										
										
										
											2016-04-30 05:24:10 +08:00
										 |  |  |  | 	if err = xml.Unmarshal(completeMultipartBytes, complMultipartUpload); err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedXML), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
											  
											
												fs: Break fs package to top-level and introduce ObjectAPI interface.
ObjectAPI interface brings in changes needed for XL ObjectAPI layer.
The new interface for any ObjectAPI layer is as below
```
// ObjectAPI interface.
type ObjectAPI interface {
        // Bucket resource API.
        DeleteBucket(bucket string) *probe.Error
        ListBuckets() ([]BucketInfo, *probe.Error)
        MakeBucket(bucket string) *probe.Error
        GetBucketInfo(bucket string) (BucketInfo, *probe.Error)
        // Bucket query API.
        ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error)
        ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error)
        // Object resource API.
        GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error)
        GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error)
        PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error)
        DeleteObject(bucket, object string) *probe.Error
        // Object query API.
        NewMultipartUpload(bucket, object string) (string, *probe.Error)
        PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error)
        ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error)
        CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error)
        AbortMultipartUpload(bucket, object, uploadID string) *probe.Error
}
```
											
										 
											2016-03-31 07:15:28 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-05-26 03:11:26 +08:00
										 |  |  |  | 	if len(complMultipartUpload.Parts) == 0 { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedXML), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-05-26 03:11:26 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-11-14 16:25:10 +08:00
										 |  |  |  | 	if !sort.IsSorted(CompletedParts(complMultipartUpload.Parts)) { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidPartOrder), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
											  
											
												fs: Break fs package to top-level and introduce ObjectAPI interface.
ObjectAPI interface brings in changes needed for XL ObjectAPI layer.
The new interface for any ObjectAPI layer is as below
```
// ObjectAPI interface.
type ObjectAPI interface {
        // Bucket resource API.
        DeleteBucket(bucket string) *probe.Error
        ListBuckets() ([]BucketInfo, *probe.Error)
        MakeBucket(bucket string) *probe.Error
        GetBucketInfo(bucket string) (BucketInfo, *probe.Error)
        // Bucket query API.
        ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error)
        ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error)
        // Object resource API.
        GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error)
        GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error)
        PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error)
        DeleteObject(bucket, object string) *probe.Error
        // Object query API.
        NewMultipartUpload(bucket, object string) (string, *probe.Error)
        PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error)
        ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error)
        CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error)
        AbortMultipartUpload(bucket, object, uploadID string) *probe.Error
}
```
											
										 
											2016-03-31 07:15:28 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 	var objectEncryptionKey []byte | 
					
						
							|  |  |  |  | 	var opts ObjectOptions | 
					
						
							|  |  |  |  | 	var isEncrypted, ssec bool | 
					
						
							|  |  |  |  | 	if objectAPI.IsEncryptionSupported() { | 
					
						
							|  |  |  |  | 		var li ListPartsInfo | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 		li, err = objectAPI.ListObjectParts(ctx, bucket, object, uploadID, 0, 1, opts) | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		if crypto.IsEncrypted(li.UserDefined) { | 
					
						
							|  |  |  |  | 			var key []byte | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 			isEncrypted = true | 
					
						
							|  |  |  |  | 			ssec = crypto.SSEC.IsEncrypted(li.UserDefined) | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 			if crypto.S3.IsEncrypted(li.UserDefined) { | 
					
						
							|  |  |  |  | 				// Calculating object encryption key
 | 
					
						
							|  |  |  |  | 				objectEncryptionKey, err = decryptObjectInfo(key, bucket, object, li.UserDefined) | 
					
						
							|  |  |  |  | 				if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 					writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 					return | 
					
						
							|  |  |  |  | 				} | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	partsMap := make(map[string]PartInfo) | 
					
						
							|  |  |  |  | 	if isEncrypted { | 
					
						
							|  |  |  |  | 		var partNumberMarker int | 
					
						
							|  |  |  |  | 		maxParts := 1000 | 
					
						
							|  |  |  |  | 		for { | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 			listPartsInfo, err := objectAPI.ListObjectParts(ctx, bucket, object, uploadID, partNumberMarker, maxParts, opts) | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 				writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 				return | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 			for _, part := range listPartsInfo.Parts { | 
					
						
							|  |  |  |  | 				partsMap[strconv.Itoa(part.PartNumber)] = part | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 			partNumberMarker = listPartsInfo.NextPartNumberMarker | 
					
						
							|  |  |  |  | 			if !listPartsInfo.IsTruncated { | 
					
						
							|  |  |  |  | 				break | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-06 06:16:43 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												fs: Break fs package to top-level and introduce ObjectAPI interface.
ObjectAPI interface brings in changes needed for XL ObjectAPI layer.
The new interface for any ObjectAPI layer is as below
```
// ObjectAPI interface.
type ObjectAPI interface {
        // Bucket resource API.
        DeleteBucket(bucket string) *probe.Error
        ListBuckets() ([]BucketInfo, *probe.Error)
        MakeBucket(bucket string) *probe.Error
        GetBucketInfo(bucket string) (BucketInfo, *probe.Error)
        // Bucket query API.
        ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error)
        ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error)
        // Object resource API.
        GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error)
        GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error)
        PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error)
        DeleteObject(bucket, object string) *probe.Error
        // Object query API.
        NewMultipartUpload(bucket, object string) (string, *probe.Error)
        PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error)
        ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error)
        CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error)
        AbortMultipartUpload(bucket, object, uploadID string) *probe.Error
}
```
											
										 
											2016-03-31 07:15:28 +08:00
										 |  |  |  | 	// Complete parts.
 | 
					
						
							| 
									
										
										
										
											2017-11-14 16:25:10 +08:00
										 |  |  |  | 	var completeParts []CompletePart | 
					
						
							| 
									
										
										
										
											2016-04-11 16:29:18 +08:00
										 |  |  |  | 	for _, part := range complMultipartUpload.Parts { | 
					
						
							| 
									
										
										
										
											2017-03-16 11:48:49 +08:00
										 |  |  |  | 		part.ETag = canonicalizeETag(part.ETag) | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 		if isEncrypted { | 
					
						
							|  |  |  |  | 			// ETag is stored in the backend in encrypted form. Validate client sent ETag with
 | 
					
						
							|  |  |  |  | 			// decrypted ETag.
 | 
					
						
							|  |  |  |  | 			if bkPartInfo, ok := partsMap[strconv.Itoa(part.PartNumber)]; ok { | 
					
						
							|  |  |  |  | 				bkETag := tryDecryptETag(objectEncryptionKey, bkPartInfo.ETag, ssec) | 
					
						
							|  |  |  |  | 				if bkETag != part.ETag { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 					writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidPart), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 					return | 
					
						
							|  |  |  |  | 				} | 
					
						
							|  |  |  |  | 				part.ETag = bkPartInfo.ETag | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-04-11 16:29:18 +08:00
										 |  |  |  | 		completeParts = append(completeParts, part) | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-29 05:14:06 +08:00
										 |  |  |  | 	completeMultiPartUpload := objectAPI.CompleteMultipartUpload | 
					
						
							| 
									
										
										
										
											2019-05-23 05:54:15 +08:00
										 |  |  |  | 	if api.CacheAPI() != nil { | 
					
						
							|  |  |  |  | 		completeMultiPartUpload = api.CacheAPI().CompleteMultipartUpload | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-02-21 14:20:15 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// This code is specifically to handle the requirements for slow
 | 
					
						
							|  |  |  |  | 	// complete multipart upload operations on FS mode.
 | 
					
						
							|  |  |  |  | 	writeErrorResponseWithoutXMLHeader := func(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) { | 
					
						
							|  |  |  |  | 		switch err.Code { | 
					
						
							|  |  |  |  | 		case "SlowDown", "XMinioServerNotInitialized", "XMinioReadQuorum", "XMinioWriteQuorum": | 
					
						
							|  |  |  |  | 			// Set retry-after header to indicate user-agents to retry request after 120secs.
 | 
					
						
							|  |  |  |  | 			// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
 | 
					
						
							|  |  |  |  | 			w.Header().Set("Retry-After", "120") | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 		// Generate error response.
 | 
					
						
							|  |  |  |  | 		errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path, | 
					
						
							|  |  |  |  | 			w.Header().Get(responseRequestIDKey), w.Header().Get(responseDeploymentIDKey)) | 
					
						
							|  |  |  |  | 		encodedErrorResponse, _ := xml.Marshal(errorResponse) | 
					
						
							|  |  |  |  | 		setCommonHeaders(w) | 
					
						
							|  |  |  |  | 		w.Header().Set("Content-Type", string(mimeXML)) | 
					
						
							|  |  |  |  | 		w.Write(encodedErrorResponse) | 
					
						
							|  |  |  |  | 		w.(http.Flusher).Flush() | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	w = &whiteSpaceWriter{ResponseWriter: w, Flusher: w.(http.Flusher)} | 
					
						
							| 
									
										
										
										
											2019-02-06 12:58:09 +08:00
										 |  |  |  | 	completeDoneCh := sendWhiteSpace(ctx, w) | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  |  | 	objInfo, err := completeMultiPartUpload(ctx, bucket, object, uploadID, completeParts, opts) | 
					
						
							| 
									
										
										
										
											2019-02-06 12:58:09 +08:00
										 |  |  |  | 	// Stop writing white spaces to the client. Note that close(doneCh) style is not used as it
 | 
					
						
							|  |  |  |  | 	// can cause white space to be written after we send XML response in a race condition.
 | 
					
						
							| 
									
										
										
										
											2019-02-21 14:20:15 +08:00
										 |  |  |  | 	headerWritten := <-completeDoneCh | 
					
						
							| 
									
										
										
										
											2015-09-19 18:20:07 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-21 14:20:15 +08:00
										 |  |  |  | 		if headerWritten { | 
					
						
							|  |  |  |  | 			writeErrorResponseWithoutXMLHeader(ctx, w, toAPIError(ctx, err), r.URL) | 
					
						
							|  |  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-06-29 05:51:49 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-08-04 07:17:21 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-06-16 11:31:06 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-13 08:08:15 +08:00
										 |  |  |  | 	// Get object location.
 | 
					
						
							| 
									
										
										
										
											2019-02-23 11:18:01 +08:00
										 |  |  |  | 	location := getObjectLocation(r, globalDomainNames, bucket, object) | 
					
						
							| 
									
										
										
										
											2016-03-02 12:01:40 +08:00
										 |  |  |  | 	// Generate complete multipart response.
 | 
					
						
							| 
									
										
										
										
											2017-05-15 03:05:51 +08:00
										 |  |  |  | 	response := generateCompleteMultpartUploadResponse(bucket, object, location, objInfo.ETag) | 
					
						
							| 
									
										
										
										
											2019-02-21 14:20:15 +08:00
										 |  |  |  | 	var encodedSuccessResponse []byte | 
					
						
							|  |  |  |  | 	if !headerWritten { | 
					
						
							|  |  |  |  | 		encodedSuccessResponse = encodeResponse(response) | 
					
						
							|  |  |  |  | 	} else { | 
					
						
							|  |  |  |  | 		encodedSuccessResponse, err = xml.Marshal(response) | 
					
						
							|  |  |  |  | 		if err != nil { | 
					
						
							|  |  |  |  | 			writeErrorResponseWithoutXMLHeader(ctx, w, toAPIError(ctx, err), r.URL) | 
					
						
							|  |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-07-01 09:48:50 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-10 23:41:02 +08:00
										 |  |  |  | 	// Set etag.
 | 
					
						
							| 
									
										
										
										
											2019-04-09 07:54:46 +08:00
										 |  |  |  | 	w.Header()["ETag"] = []string{"\"" + objInfo.ETag + "\""} | 
					
						
							| 
									
										
										
										
											2016-11-10 23:41:02 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  |  | 	// Write success response.
 | 
					
						
							| 
									
										
										
										
											2017-01-06 16:37:00 +08:00
										 |  |  |  | 	writeSuccessResponseXML(w, encodedSuccessResponse) | 
					
						
							| 
									
										
										
										
											2016-07-24 13:51:12 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-23 09:44:35 +08:00
										 |  |  |  | 	// Get host and port from Request.RemoteAddr.
 | 
					
						
							| 
									
										
										
										
											2019-02-19 16:44:26 +08:00
										 |  |  |  | 	if objectAPI.IsEncryptionSupported() { | 
					
						
							|  |  |  |  | 		if crypto.IsEncrypted(objInfo.UserDefined) { | 
					
						
							|  |  |  |  | 			objInfo.Size, _ = objInfo.DecryptedSize() | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-03-26 02:45:42 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-29 13:46:19 +08:00
										 |  |  |  | 	// Notify object created event.
 | 
					
						
							| 
									
										
										
										
											2018-03-16 04:03:41 +08:00
										 |  |  |  | 	sendEvent(eventArgs{ | 
					
						
							| 
									
										
										
										
											2018-11-03 09:40:08 +08:00
										 |  |  |  | 		EventName:    event.ObjectCreatedCompleteMultipartUpload, | 
					
						
							|  |  |  |  | 		BucketName:   bucket, | 
					
						
							|  |  |  |  | 		Object:       objInfo, | 
					
						
							|  |  |  |  | 		ReqParams:    extractReqParams(r), | 
					
						
							|  |  |  |  | 		RespElements: extractRespElements(w), | 
					
						
							|  |  |  |  | 		UserAgent:    r.UserAgent(), | 
					
						
							| 
									
										
										
										
											2019-03-26 02:45:42 +08:00
										 |  |  |  | 		Host:         handlers.GetSourceIP(r), | 
					
						
							| 
									
										
										
										
											2016-09-29 13:46:19 +08:00
										 |  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2015-05-08 10:55:30 +08:00
										 |  |  |  | } | 
					
						
							| 
									
										
										
										
											2015-06-09 02:06:06 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-13 03:45:15 +08:00
										 |  |  |  | /// Delete objectAPIHandlers
 | 
					
						
							| 
									
										
										
										
											2015-06-09 02:06:06 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-06 08:43:48 +08:00
										 |  |  |  | // DeleteObjectHandler - delete an object
 | 
					
						
							| 
									
										
										
										
											2016-04-13 03:45:15 +08:00
										 |  |  |  | func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2018-07-21 09:46:32 +08:00
										 |  |  |  | 	ctx := newContext(r, w, "DeleteObject") | 
					
						
							| 
									
										
										
										
											2018-03-15 03:01:47 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-22 12:03:24 +08:00
										 |  |  |  | 	defer logger.AuditLog(w, r, "DeleteObject", mustGetClaimsFromToken(r)) | 
					
						
							| 
									
										
										
										
											2018-10-13 03:25:59 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-16 09:42:39 +08:00
										 |  |  |  | 	vars := mux.Vars(r) | 
					
						
							| 
									
										
										
										
											2015-10-17 02:26:01 +08:00
										 |  |  |  | 	bucket := vars["bucket"] | 
					
						
							|  |  |  |  | 	object := vars["object"] | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 	objectAPI := api.ObjectAPI() | 
					
						
							|  |  |  |  | 	if objectAPI == nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2016-08-11 09:47:49 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 06:53:30 +08:00
										 |  |  |  | 	if s3Error := checkRequestAuthType(ctx, r, policy.DeleteObjectAction, bucket, object); s3Error != ErrNone { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
											
												accessPolicy: Implement Put, Get, Delete access policy.
This patch implements Get,Put,Delete bucket policies
Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
Currently supports following actions.
   "*":                             true,
   "s3:*":                          true,
   "s3:GetObject":                  true,
   "s3:ListBucket":                 true,
   "s3:PutObject":                  true,
   "s3:CreateBucket":               true,
   "s3:GetBucketLocation":          true,
   "s3:DeleteBucket":               true,
   "s3:DeleteObject":               true,
   "s3:AbortMultipartUpload":       true,
   "s3:ListBucketMultipartUploads": true,
   "s3:ListMultipartUploadParts":   true,
following conditions for "StringEquals" and "StringNotEquals"
   "s3:prefix", "s3:max-keys"
											
										 
											2016-02-04 08:46:56 +08:00
										 |  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2016-02-05 04:52:25 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-11-22 05:51:05 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-14 14:04:37 +08:00
										 |  |  |  | 	if vid := r.URL.Query().Get("versionId"); vid != "" && vid != "null" { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNoSuchVersion), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-12-14 14:04:37 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 07:44:45 +08:00
										 |  |  |  | 	// Deny if WORM is enabled
 | 
					
						
							|  |  |  |  | 	if globalWORMEnabled { | 
					
						
							|  |  |  |  | 		// Not required to check whether given object exists or not, because
 | 
					
						
							|  |  |  |  | 		// DeleteObject is always successful irrespective of object existence.
 | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 		writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-03-28 07:44:45 +08:00
										 |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-01 13:35:43 +08:00
										 |  |  |  | 	if globalDNSConfig != nil { | 
					
						
							|  |  |  |  | 		_, err := globalDNSConfig.Get(bucket) | 
					
						
							|  |  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-07-01 13:35:43 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-27 14:27:48 +08:00
										 |  |  |  | 	// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectDELETE.html
 | 
					
						
							| 
									
										
										
										
											2018-10-31 07:07:57 +08:00
										 |  |  |  | 	if err := deleteObject(ctx, objectAPI, api.CacheAPI(), bucket, object, r); err != nil { | 
					
						
							| 
									
										
										
										
											2019-02-12 17:25:52 +08:00
										 |  |  |  | 		switch err.(type) { | 
					
						
							|  |  |  |  | 		case BucketNotFound: | 
					
						
							| 
									
										
										
										
											2018-10-31 07:07:57 +08:00
										 |  |  |  | 			// When bucket doesn't exist specially handle it.
 | 
					
						
							| 
									
										
										
										
											2019-02-14 08:07:21 +08:00
										 |  |  |  | 			writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) | 
					
						
							| 
									
										
										
										
											2018-10-31 07:07:57 +08:00
										 |  |  |  | 			return | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		// Ignore delete object errors while replying to client, since we are suppposed to reply only 204.
 | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-10-17 11:02:37 +08:00
										 |  |  |  | 	writeSuccessNoContent(w) | 
					
						
							| 
									
										
										
										
											2015-06-09 02:06:06 +08:00
										 |  |  |  | } |