mirror of https://github.com/minio/minio.git
				
				
				
			
		
			
				
	
	
		
			952 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			952 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
|  * Minio Cloud Storage, (C) 2017 Minio, Inc.
 | |
|  *
 | |
|  * Licensed under the Apache License, Version 2.0 (the "License");
 | |
|  * you may not use this file except in compliance with the License.
 | |
|  * You may obtain a copy of the License at
 | |
|  *
 | |
|  *     http://www.apache.org/licenses/LICENSE-2.0
 | |
|  *
 | |
|  * Unless required by applicable law or agreed to in writing, software
 | |
|  * distributed under the License is distributed on an "AS IS" BASIS,
 | |
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|  * See the License for the specific language governing permissions and
 | |
|  * limitations under the License.
 | |
|  */
 | |
| 
 | |
| package cmd
 | |
| 
 | |
| import (
 | |
| 	"io"
 | |
| 	"io/ioutil"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"strconv"
 | |
| 
 | |
| 	"encoding/hex"
 | |
| 	"encoding/json"
 | |
| 
 | |
| 	router "github.com/gorilla/mux"
 | |
| 	"github.com/minio/minio-go/pkg/policy"
 | |
| 	"github.com/minio/minio/pkg/hash"
 | |
| )
 | |
| 
 | |
| // GetObjectHandler - GET Object
 | |
| // ----------
 | |
| // This implementation of the GET operation retrieves object. To use GET,
 | |
| // you must have READ access to the object.
 | |
| func (api gatewayAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	var object, bucket string
 | |
| 	vars := router.Vars(r)
 | |
| 	bucket = vars["bucket"]
 | |
| 	object = vars["object"]
 | |
| 
 | |
| 	// Fetch object stat info.
 | |
| 	objectAPI := api.ObjectAPI()
 | |
| 	if objectAPI == nil {
 | |
| 		writeErrorResponse(w, ErrServerNotInitialized, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	reqAuthType := getRequestAuthType(r)
 | |
| 
 | |
| 	switch reqAuthType {
 | |
| 	case authTypePresignedV2, authTypeSignedV2:
 | |
| 		// Signature V2 validation.
 | |
| 		s3Error := isReqAuthenticatedV2(r)
 | |
| 		if s3Error != ErrNone {
 | |
| 			errorIf(errSignatureMismatch, "%s", dumpRequest(r))
 | |
| 			writeErrorResponse(w, s3Error, r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 	case authTypeSigned, authTypePresigned:
 | |
| 		s3Error := isReqAuthenticated(r, serverConfig.GetRegion())
 | |
| 		if s3Error != ErrNone {
 | |
| 			errorIf(errSignatureMismatch, "%s", dumpRequest(r))
 | |
| 			writeErrorResponse(w, s3Error, r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 	case authTypeAnonymous:
 | |
| 		// No verification needed for anonymous requests.
 | |
| 	default:
 | |
| 		// For all unknown auth types return error.
 | |
| 		writeErrorResponse(w, ErrAccessDenied, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	getObjectInfo := objectAPI.GetObjectInfo
 | |
| 	if reqAuthType == authTypeAnonymous {
 | |
| 		getObjectInfo = objectAPI.AnonGetObjectInfo
 | |
| 	}
 | |
| 	objInfo, err := getObjectInfo(bucket, object)
 | |
| 	if err != nil {
 | |
| 		errorIf(err, "Unable to fetch object info.")
 | |
| 		apiErr := toAPIErrorCode(err)
 | |
| 		if apiErr == ErrNoSuchKey {
 | |
| 			apiErr = errAllowableObjectNotFound(bucket, r)
 | |
| 		}
 | |
| 		writeErrorResponse(w, apiErr, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Get request range.
 | |
| 	var hrange *httpRange
 | |
| 	rangeHeader := r.Header.Get("Range")
 | |
| 	if rangeHeader != "" {
 | |
| 		if hrange, err = parseRequestRange(rangeHeader, objInfo.Size); err != nil {
 | |
| 			// Handle only errInvalidRange
 | |
| 			// Ignore other parse error and treat it as regular Get request like Amazon S3.
 | |
| 			if err == errInvalidRange {
 | |
| 				writeErrorResponse(w, ErrInvalidRange, r.URL)
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			// log the error.
 | |
| 			errorIf(err, "Invalid request range")
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Validate pre-conditions if any.
 | |
| 	if checkPreconditions(w, r, objInfo) {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Get the object.
 | |
| 	var startOffset int64
 | |
| 	length := objInfo.Size
 | |
| 	if hrange != nil {
 | |
| 		startOffset = hrange.offsetBegin
 | |
| 		length = hrange.getLength()
 | |
| 	}
 | |
| 	// Indicates if any data was written to the http.ResponseWriter
 | |
| 	dataWritten := false
 | |
| 	// io.Writer type which keeps track if any data was written.
 | |
| 	writer := funcToWriter(func(p []byte) (int, error) {
 | |
| 		if !dataWritten {
 | |
| 			// Set headers on the first write.
 | |
| 			// Set standard object headers.
 | |
| 			setObjectHeaders(w, objInfo, hrange)
 | |
| 
 | |
| 			// Set any additional requested response headers.
 | |
| 			setHeadGetRespHeaders(w, r.URL.Query())
 | |
| 
 | |
| 			dataWritten = true
 | |
| 		}
 | |
| 		return w.Write(p)
 | |
| 	})
 | |
| 
 | |
| 	getObject := objectAPI.GetObject
 | |
| 	if reqAuthType == authTypeAnonymous {
 | |
| 		getObject = objectAPI.AnonGetObject
 | |
| 	}
 | |
| 
 | |
| 	// Reads the object at startOffset and writes to mw.
 | |
| 	if err = getObject(bucket, object, startOffset, length, writer); err != nil {
 | |
| 		errorIf(err, "Unable to write to client.")
 | |
| 		if !dataWritten {
 | |
| 			// Error response only if no data has been written to client yet. i.e if
 | |
| 			// partial data has already been written before an error
 | |
| 			// occurred then no point in setting StatusCode and
 | |
| 			// sending error XML.
 | |
| 			writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 	if !dataWritten {
 | |
| 		// If ObjectAPI.GetObject did not return error and no data has
 | |
| 		// been written it would mean that it is a 0-byte object.
 | |
| 		// call wrter.Write(nil) to set appropriate headers.
 | |
| 		writer.Write(nil)
 | |
| 	}
 | |
| 
 | |
| 	// Get host and port from Request.RemoteAddr.
 | |
| 	host, port, err := net.SplitHostPort(r.RemoteAddr)
 | |
| 	if err != nil {
 | |
| 		host, port = "", ""
 | |
| 	}
 | |
| 
 | |
| 	// Notify object accessed via a GET request.
 | |
| 	eventNotify(eventData{
 | |
| 		Type:      ObjectAccessedGet,
 | |
| 		Bucket:    bucket,
 | |
| 		ObjInfo:   objInfo,
 | |
| 		ReqParams: extractReqParams(r),
 | |
| 		UserAgent: r.UserAgent(),
 | |
| 		Host:      host,
 | |
| 		Port:      port,
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // PutObjectHandler - PUT Object
 | |
| // ----------
 | |
| // This implementation of the PUT operation adds an object to a bucket.
 | |
| func (api gatewayAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	objectAPI := api.ObjectAPI()
 | |
| 	if objectAPI == nil {
 | |
| 		writeErrorResponse(w, ErrServerNotInitialized, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// X-Amz-Copy-Source shouldn't be set for this call.
 | |
| 	if _, ok := r.Header["X-Amz-Copy-Source"]; ok {
 | |
| 		writeErrorResponse(w, ErrInvalidCopySource, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	var object, bucket string
 | |
| 	vars := router.Vars(r)
 | |
| 	bucket = vars["bucket"]
 | |
| 	object = vars["object"]
 | |
| 
 | |
| 	// TODO: we should validate the object name here
 | |
| 
 | |
| 	// Get Content-Md5 sent by client and verify if valid
 | |
| 	md5Bytes, err := checkValidMD5(r.Header.Get("Content-Md5"))
 | |
| 	if err != nil {
 | |
| 		errorIf(err, "Unable to validate content-md5 format.")
 | |
| 		writeErrorResponse(w, ErrInvalidDigest, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	/// if Content-Length is unknown/missing, deny the request
 | |
| 	size := r.ContentLength
 | |
| 	reqAuthType := getRequestAuthType(r)
 | |
| 	if reqAuthType == authTypeStreamingSigned {
 | |
| 		sizeStr := r.Header.Get("x-amz-decoded-content-length")
 | |
| 		size, err = strconv.ParseInt(sizeStr, 10, 64)
 | |
| 		if err != nil {
 | |
| 			errorIf(err, "Unable to parse `x-amz-decoded-content-length` into its integer value", sizeStr)
 | |
| 			writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 	if size == -1 {
 | |
| 		writeErrorResponse(w, ErrMissingContentLength, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	/// maximum Upload size for objects in a single operation
 | |
| 	if isMaxObjectSize(size) {
 | |
| 		writeErrorResponse(w, ErrEntityTooLarge, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Extract metadata to be saved from incoming HTTP header.
 | |
| 	metadata, err := extractMetadataFromHeader(r.Header)
 | |
| 	if err != nil {
 | |
| 		errorIf(err, "found invalid http request header")
 | |
| 		writeErrorResponse(w, ErrInternalError, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 	if reqAuthType == authTypeStreamingSigned {
 | |
| 		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")
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Lock the object.
 | |
| 	objectLock := globalNSMutex.NewNSLock(bucket, object)
 | |
| 	if objectLock.GetLock(globalOperationTimeout) != nil {
 | |
| 		writeErrorResponse(w, ErrOperationTimedOut, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 	defer objectLock.Unlock()
 | |
| 
 | |
| 	var (
 | |
| 		// Make sure we hex encode md5sum here.
 | |
| 		md5hex    = hex.EncodeToString(md5Bytes)
 | |
| 		sha256hex = ""
 | |
| 		putObject = objectAPI.PutObject
 | |
| 		reader    = r.Body
 | |
| 	)
 | |
| 
 | |
| 	switch reqAuthType {
 | |
| 	default:
 | |
| 		// For all unknown auth types return error.
 | |
| 		writeErrorResponse(w, ErrAccessDenied, r.URL)
 | |
| 		return
 | |
| 	case authTypeAnonymous:
 | |
| 		putObject = objectAPI.AnonPutObject
 | |
| 	case authTypeStreamingSigned:
 | |
| 		// Initialize stream signature verifier.
 | |
| 		var s3Error APIErrorCode
 | |
| 		reader, s3Error = newSignV4ChunkedReader(r)
 | |
| 		if s3Error != ErrNone {
 | |
| 			errorIf(errSignatureMismatch, "%s", dumpRequest(r))
 | |
| 			writeErrorResponse(w, s3Error, r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 	case authTypeSignedV2, authTypePresignedV2:
 | |
| 		s3Error := isReqAuthenticatedV2(r)
 | |
| 		if s3Error != ErrNone {
 | |
| 			errorIf(errSignatureMismatch, "%s", dumpRequest(r))
 | |
| 			writeErrorResponse(w, s3Error, r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 	case authTypePresigned, authTypeSigned:
 | |
| 		if s3Error := reqSignatureV4Verify(r, serverConfig.GetRegion()); s3Error != ErrNone {
 | |
| 			errorIf(errSignatureMismatch, "%s", dumpRequest(r))
 | |
| 			writeErrorResponse(w, s3Error, r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 		if !skipContentSha256Cksum(r) {
 | |
| 			sha256hex = getContentSha256Cksum(r)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	hashReader, err := hash.NewReader(reader, size, md5hex, sha256hex)
 | |
| 	if err != nil {
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	objInfo, err := putObject(bucket, object, hashReader, metadata)
 | |
| 	if err != nil {
 | |
| 		errorIf(err, "Unable to save an object %s", r.URL.Path)
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	w.Header().Set("ETag", "\""+objInfo.ETag+"\"")
 | |
| 	writeSuccessResponseHeadersOnly(w)
 | |
| 
 | |
| 	// Get host and port from Request.RemoteAddr.
 | |
| 	host, port, err := net.SplitHostPort(r.RemoteAddr)
 | |
| 	if err != nil {
 | |
| 		host, port = "", ""
 | |
| 	}
 | |
| 
 | |
| 	// Notify object created event.
 | |
| 	eventNotify(eventData{
 | |
| 		Type:      ObjectCreatedPut,
 | |
| 		Bucket:    bucket,
 | |
| 		ObjInfo:   objInfo,
 | |
| 		ReqParams: extractReqParams(r),
 | |
| 		UserAgent: r.UserAgent(),
 | |
| 		Host:      host,
 | |
| 		Port:      port,
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // HeadObjectHandler - HEAD Object
 | |
| // -----------
 | |
| // The HEAD operation retrieves metadata from an object without returning the object itself.
 | |
| func (api gatewayAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	var object, bucket string
 | |
| 	vars := router.Vars(r)
 | |
| 	bucket = vars["bucket"]
 | |
| 	object = vars["object"]
 | |
| 
 | |
| 	objectAPI := api.ObjectAPI()
 | |
| 	if objectAPI == nil {
 | |
| 		writeErrorResponseHeadersOnly(w, ErrServerNotInitialized)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	reqAuthType := getRequestAuthType(r)
 | |
| 
 | |
| 	switch reqAuthType {
 | |
| 	case authTypePresignedV2, authTypeSignedV2:
 | |
| 		// Signature V2 validation.
 | |
| 		s3Error := isReqAuthenticatedV2(r)
 | |
| 		if s3Error != ErrNone {
 | |
| 			errorIf(errSignatureMismatch, "%s", dumpRequest(r))
 | |
| 			writeErrorResponse(w, s3Error, r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 	case authTypeSigned, authTypePresigned:
 | |
| 		s3Error := isReqAuthenticated(r, serverConfig.GetRegion())
 | |
| 		if s3Error != ErrNone {
 | |
| 			errorIf(errSignatureMismatch, "%s", dumpRequest(r))
 | |
| 			writeErrorResponse(w, s3Error, r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 	case authTypeAnonymous:
 | |
| 		// No verification needed for anonymous requests.
 | |
| 	default:
 | |
| 		// For all unknown auth types return error.
 | |
| 		writeErrorResponse(w, ErrAccessDenied, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	getObjectInfo := objectAPI.GetObjectInfo
 | |
| 	if reqAuthType == authTypeAnonymous {
 | |
| 		getObjectInfo = objectAPI.AnonGetObjectInfo
 | |
| 	}
 | |
| 	objInfo, err := getObjectInfo(bucket, object)
 | |
| 	if err != nil {
 | |
| 		errorIf(err, "Unable to fetch object info.")
 | |
| 		apiErr := toAPIErrorCode(err)
 | |
| 		if apiErr == ErrNoSuchKey {
 | |
| 			apiErr = errAllowableObjectNotFound(bucket, r)
 | |
| 		}
 | |
| 		writeErrorResponse(w, apiErr, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Validate pre-conditions if any.
 | |
| 	if checkPreconditions(w, r, objInfo) {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Set standard object headers.
 | |
| 	setObjectHeaders(w, objInfo, nil)
 | |
| 
 | |
| 	// Set any additional requested response headers.
 | |
| 	setHeadGetRespHeaders(w, r.URL.Query())
 | |
| 
 | |
| 	// Successful response.
 | |
| 	w.WriteHeader(http.StatusOK)
 | |
| 
 | |
| 	// Get host and port from Request.RemoteAddr.
 | |
| 	host, port, err := net.SplitHostPort(r.RemoteAddr)
 | |
| 	if err != nil {
 | |
| 		host, port = "", ""
 | |
| 	}
 | |
| 
 | |
| 	// Notify object accessed via a HEAD request.
 | |
| 	eventNotify(eventData{
 | |
| 		Type:      ObjectAccessedHead,
 | |
| 		Bucket:    bucket,
 | |
| 		ObjInfo:   objInfo,
 | |
| 		ReqParams: extractReqParams(r),
 | |
| 		UserAgent: r.UserAgent(),
 | |
| 		Host:      host,
 | |
| 		Port:      port,
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // PutBucketPolicyHandler - PUT Bucket policy
 | |
| // -----------------
 | |
| // This implementation of the PUT operation uses the policy
 | |
| // subresource to add to or replace a policy on a bucket
 | |
| func (api gatewayAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	objAPI := api.ObjectAPI()
 | |
| 	if objAPI == nil {
 | |
| 		writeErrorResponse(w, ErrServerNotInitialized, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone {
 | |
| 		writeErrorResponse(w, s3Error, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	vars := router.Vars(r)
 | |
| 	bucket := vars["bucket"]
 | |
| 
 | |
| 	// Before proceeding validate if bucket exists.
 | |
| 	_, err := objAPI.GetBucketInfo(bucket)
 | |
| 	if err != nil {
 | |
| 		errorIf(err, "Unable to find bucket info.")
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// If Content-Length is unknown or zero, deny the
 | |
| 	// request. PutBucketPolicy always needs a Content-Length.
 | |
| 	if r.ContentLength == -1 || r.ContentLength == 0 {
 | |
| 		writeErrorResponse(w, ErrMissingContentLength, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 	// If Content-Length is greater than maximum allowed policy size.
 | |
| 	if r.ContentLength > maxAccessPolicySize {
 | |
| 		writeErrorResponse(w, ErrEntityTooLarge, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Read access policy up to maxAccessPolicySize.
 | |
| 	// http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
 | |
| 	// bucket policies are limited to 20KB in size, using a limit reader.
 | |
| 	policyBytes, err := ioutil.ReadAll(io.LimitReader(r.Body, maxAccessPolicySize))
 | |
| 	if err != nil {
 | |
| 		errorIf(err, "Unable to read from client.")
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	policyInfo := policy.BucketAccessPolicy{}
 | |
| 	if err = json.Unmarshal(policyBytes, &policyInfo); err != nil {
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if err = objAPI.SetBucketPolicies(bucket, policyInfo); err != nil {
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 	// Success.
 | |
| 	writeSuccessNoContent(w)
 | |
| }
 | |
| 
 | |
| // DeleteBucketPolicyHandler - DELETE Bucket policy
 | |
| // -----------------
 | |
| // This implementation of the DELETE operation uses the policy
 | |
| // subresource to add to remove a policy on a bucket.
 | |
| func (api gatewayAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	objAPI := api.ObjectAPI()
 | |
| 	if objAPI == nil {
 | |
| 		writeErrorResponse(w, ErrServerNotInitialized, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone {
 | |
| 		writeErrorResponse(w, s3Error, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	vars := router.Vars(r)
 | |
| 	bucket := vars["bucket"]
 | |
| 
 | |
| 	// Before proceeding validate if bucket exists.
 | |
| 	_, err := objAPI.GetBucketInfo(bucket)
 | |
| 	if err != nil {
 | |
| 		errorIf(err, "Unable to find bucket info.")
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Delete bucket access policy, by passing an empty policy
 | |
| 	// struct.
 | |
| 	objAPI.DeleteBucketPolicies(bucket)
 | |
| 	// Success.
 | |
| 	writeSuccessNoContent(w)
 | |
| }
 | |
| 
 | |
| // GetBucketPolicyHandler - GET Bucket policy
 | |
| // -----------------
 | |
| // This operation uses the policy
 | |
| // subresource to return the policy of a specified bucket.
 | |
| func (api gatewayAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	objAPI := api.ObjectAPI()
 | |
| 	if objAPI == nil {
 | |
| 		writeErrorResponse(w, ErrServerNotInitialized, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone {
 | |
| 		writeErrorResponse(w, s3Error, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	vars := router.Vars(r)
 | |
| 	bucket := vars["bucket"]
 | |
| 
 | |
| 	// Before proceeding validate if bucket exists.
 | |
| 	_, err := objAPI.GetBucketInfo(bucket)
 | |
| 	if err != nil {
 | |
| 		errorIf(err, "Unable to find bucket info.")
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	bp, err := objAPI.GetBucketPolicies(bucket)
 | |
| 	if err != nil {
 | |
| 		errorIf(err, "Unable to read bucket policy.")
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	policyBytes, err := json.Marshal(bp)
 | |
| 	if err != nil {
 | |
| 		errorIf(err, "Unable to read bucket policy.")
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 	// Write to client.
 | |
| 	w.Write(policyBytes)
 | |
| }
 | |
| 
 | |
| // GetBucketNotificationHandler - This implementation of the GET
 | |
| // operation uses the notification subresource to return the
 | |
| // notification configuration of a bucket. If notifications are
 | |
| // not enabled on the bucket, the operation returns an empty
 | |
| // NotificationConfiguration element.
 | |
| func (api gatewayAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	writeErrorResponse(w, ErrNotImplemented, r.URL)
 | |
| }
 | |
| 
 | |
| // PutBucketNotificationHandler - Minio notification feature enables
 | |
| // you to receive notifications when certain events happen in your bucket.
 | |
| // Using this API, you can replace an existing notification configuration.
 | |
| // The configuration is an XML file that defines the event types that you
 | |
| // want Minio to publish and the destination where you want Minio to publish
 | |
| // an event notification when it detects an event of the specified type.
 | |
| // By default, your bucket has no event notifications configured. That is,
 | |
| // the notification configuration will be an empty NotificationConfiguration.
 | |
| func (api gatewayAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	writeErrorResponse(w, ErrNotImplemented, r.URL)
 | |
| }
 | |
| 
 | |
| // ListenBucketNotificationHandler - list bucket notifications.
 | |
| func (api gatewayAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	writeErrorResponse(w, ErrNotImplemented, r.URL)
 | |
| }
 | |
| 
 | |
| // PutBucketHandler - PUT Bucket
 | |
| // ----------
 | |
| // This implementation of the PUT operation creates a new bucket for authenticated request
 | |
| func (api gatewayAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	objectAPI := api.ObjectAPI()
 | |
| 	if objectAPI == nil {
 | |
| 		writeErrorResponse(w, ErrServerNotInitialized, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// PutBucket does not have any bucket action.
 | |
| 	s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion())
 | |
| 	if s3Error != ErrNone {
 | |
| 		writeErrorResponse(w, s3Error, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	vars := router.Vars(r)
 | |
| 	bucket := vars["bucket"]
 | |
| 
 | |
| 	// Validate if incoming location constraint is valid, reject
 | |
| 	// requests which do not follow valid region requirements.
 | |
| 	location, s3Error := parseLocationConstraint(r)
 | |
| 	if s3Error != ErrNone {
 | |
| 		writeErrorResponse(w, s3Error, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	bucketLock := globalNSMutex.NewNSLock(bucket, "")
 | |
| 	if bucketLock.GetLock(globalOperationTimeout) != nil {
 | |
| 		writeErrorResponse(w, ErrOperationTimedOut, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 	defer bucketLock.Unlock()
 | |
| 
 | |
| 	// Proceed to creating a bucket.
 | |
| 	err := objectAPI.MakeBucketWithLocation(bucket, location)
 | |
| 	if err != nil {
 | |
| 		errorIf(err, "Unable to create a bucket.")
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Make sure to add Location information here only for bucket
 | |
| 	w.Header().Set("Location", getLocation(r))
 | |
| 
 | |
| 	writeSuccessResponseHeadersOnly(w)
 | |
| }
 | |
| 
 | |
| // DeleteBucketHandler - Delete bucket
 | |
| func (api gatewayAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	objectAPI := api.ObjectAPI()
 | |
| 	if objectAPI == nil {
 | |
| 		writeErrorResponse(w, ErrServerNotInitialized, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// DeleteBucket does not have any bucket action.
 | |
| 	if s3Error := checkRequestAuthType(r, "", "", serverConfig.GetRegion()); s3Error != ErrNone {
 | |
| 		writeErrorResponse(w, s3Error, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	vars := router.Vars(r)
 | |
| 	bucket := vars["bucket"]
 | |
| 
 | |
| 	// Attempt to delete bucket.
 | |
| 	if err := objectAPI.DeleteBucket(bucket); err != nil {
 | |
| 		errorIf(err, "Unable to delete a bucket.")
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Write success response.
 | |
| 	writeSuccessNoContent(w)
 | |
| }
 | |
| 
 | |
| // ListObjectsV1Handler - GET Bucket (List Objects) Version 1.
 | |
| // --------------------------
 | |
| // This implementation of the GET operation returns some or all (up to 1000)
 | |
| // of the objects in a bucket. You can use the request parameters as selection
 | |
| // criteria to return a subset of the objects in a bucket.
 | |
| //
 | |
| func (api gatewayAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) {
 | |
| 	vars := router.Vars(r)
 | |
| 	bucket := vars["bucket"]
 | |
| 
 | |
| 	objectAPI := api.ObjectAPI()
 | |
| 	if objectAPI == nil {
 | |
| 		writeErrorResponse(w, ErrServerNotInitialized, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	reqAuthType := getRequestAuthType(r)
 | |
| 
 | |
| 	switch reqAuthType {
 | |
| 	case authTypePresignedV2, authTypeSignedV2:
 | |
| 		// Signature V2 validation.
 | |
| 		s3Error := isReqAuthenticatedV2(r)
 | |
| 		if s3Error != ErrNone {
 | |
| 			errorIf(errSignatureMismatch, "%s", dumpRequest(r))
 | |
| 			writeErrorResponse(w, s3Error, r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 	case authTypeSigned, authTypePresigned:
 | |
| 		s3Error := isReqAuthenticated(r, serverConfig.GetRegion())
 | |
| 		if s3Error != ErrNone {
 | |
| 			errorIf(errSignatureMismatch, "%s", dumpRequest(r))
 | |
| 			writeErrorResponse(w, s3Error, r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 	case authTypeAnonymous:
 | |
| 		// No verification needed for anonymous requests.
 | |
| 	default:
 | |
| 		// For all unknown auth types return error.
 | |
| 		writeErrorResponse(w, ErrAccessDenied, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Extract all the listObjectsV1 query params to their native
 | |
| 	// values.  N B We delegate validation of params to respective
 | |
| 	// gateway backends.
 | |
| 	prefix, marker, delimiter, maxKeys, _ := getListObjectsV1Args(r.URL.Query())
 | |
| 
 | |
| 	// Validate the maxKeys lowerbound. When maxKeys > 1000, S3 returns 1000 but
 | |
| 	// does not throw an error.
 | |
| 	if maxKeys < 0 {
 | |
| 		writeErrorResponse(w, ErrInvalidMaxKeys, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	listObjects := objectAPI.ListObjects
 | |
| 	if reqAuthType == authTypeAnonymous {
 | |
| 		listObjects = objectAPI.AnonListObjects
 | |
| 	}
 | |
| 	// Inititate a list objects operation based on the input params.
 | |
| 	// On success would return back ListObjectsInfo object to be
 | |
| 	// marshalled into S3 compatible XML header.
 | |
| 	listObjectsInfo, err := listObjects(bucket, prefix, marker, delimiter, maxKeys)
 | |
| 	if err != nil {
 | |
| 		errorIf(err, "Unable to list objects.")
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 	response := generateListObjectsV1Response(bucket, prefix, marker, delimiter, maxKeys, listObjectsInfo)
 | |
| 	// Write success response.
 | |
| 	writeSuccessResponseXML(w, encodeResponse(response))
 | |
| }
 | |
| 
 | |
| // ListObjectsV2Handler - GET Bucket (List Objects) Version 2.
 | |
| // --------------------------
 | |
| // This implementation of the GET operation returns some or all (up to 1000)
 | |
| // of the objects in a bucket. You can use the request parameters as selection
 | |
| // criteria to return a subset of the objects in a bucket.
 | |
| //
 | |
| // NOTE: It is recommended that this API to be used for application development.
 | |
| // Minio continues to support ListObjectsV1 for supporting legacy tools.
 | |
| func (api gatewayAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) {
 | |
| 	vars := router.Vars(r)
 | |
| 	bucket := vars["bucket"]
 | |
| 
 | |
| 	objectAPI := api.ObjectAPI()
 | |
| 	if objectAPI == nil {
 | |
| 		writeErrorResponse(w, ErrServerNotInitialized, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	reqAuthType := getRequestAuthType(r)
 | |
| 
 | |
| 	switch reqAuthType {
 | |
| 	case authTypePresignedV2, authTypeSignedV2:
 | |
| 		// Signature V2 validation.
 | |
| 		s3Error := isReqAuthenticatedV2(r)
 | |
| 		if s3Error != ErrNone {
 | |
| 			errorIf(errSignatureMismatch, dumpRequest(r))
 | |
| 			writeErrorResponse(w, s3Error, r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 	case authTypeSigned, authTypePresigned:
 | |
| 		s3Error := isReqAuthenticated(r, serverConfig.GetRegion())
 | |
| 		if s3Error != ErrNone {
 | |
| 			errorIf(errSignatureMismatch, dumpRequest(r))
 | |
| 			writeErrorResponse(w, s3Error, r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 	case authTypeAnonymous:
 | |
| 		// No verification needed for anonymous requests.
 | |
| 	default:
 | |
| 		// For all unknown auth types return error.
 | |
| 		writeErrorResponse(w, ErrAccessDenied, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Extract all the listObjectsV2 query params to their native values.
 | |
| 	prefix, token, startAfter, delimiter, fetchOwner, maxKeys, _ := getListObjectsV2Args(r.URL.Query())
 | |
| 
 | |
| 	// In ListObjectsV2 'continuation-token' is the marker.
 | |
| 	marker := token
 | |
| 	// Check if 'continuation-token' is empty.
 | |
| 	if token == "" {
 | |
| 		// Then we need to use 'start-after' as marker instead.
 | |
| 		marker = startAfter
 | |
| 	}
 | |
| 
 | |
| 	listObjectsV2 := objectAPI.ListObjectsV2
 | |
| 	if reqAuthType == authTypeAnonymous {
 | |
| 		listObjectsV2 = objectAPI.AnonListObjectsV2
 | |
| 	}
 | |
| 
 | |
| 	// Validate the query params before beginning to serve the request.
 | |
| 	// fetch-owner is not validated since it is a boolean
 | |
| 	if s3Error := validateGatewayListObjectsV2Args(prefix, marker, delimiter, maxKeys); s3Error != ErrNone {
 | |
| 		writeErrorResponse(w, s3Error, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 	// Inititate a list objects operation based on the input params.
 | |
| 	// On success would return back ListObjectsV2Info object to be
 | |
| 	// serialized as XML and sent as S3 compatible response body.
 | |
| 	listObjectsV2Info, err := listObjectsV2(bucket, prefix, token, delimiter, maxKeys, fetchOwner, startAfter)
 | |
| 	if err != nil {
 | |
| 		errorIf(err, "Unable to list objects. Args to listObjectsV2 are bucket=%s, prefix=%s, token=%s, delimiter=%s", bucket, prefix, token, delimiter)
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	response := generateListObjectsV2Response(bucket, prefix, token, listObjectsV2Info.NextContinuationToken, startAfter, delimiter, fetchOwner, listObjectsV2Info.IsTruncated, maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes)
 | |
| 	// Write success response.
 | |
| 	writeSuccessResponseXML(w, encodeResponse(response))
 | |
| }
 | |
| 
 | |
| // HeadBucketHandler - HEAD Bucket
 | |
| // ----------
 | |
| // This operation is useful to determine if a bucket exists.
 | |
| // The operation returns a 200 OK if the bucket exists and you
 | |
| // have permission to access it. Otherwise, the operation might
 | |
| // return responses such as 404 Not Found and 403 Forbidden.
 | |
| func (api gatewayAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	vars := router.Vars(r)
 | |
| 	bucket := vars["bucket"]
 | |
| 
 | |
| 	objectAPI := api.ObjectAPI()
 | |
| 	if objectAPI == nil {
 | |
| 		writeErrorResponseHeadersOnly(w, ErrServerNotInitialized)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	reqAuthType := getRequestAuthType(r)
 | |
| 
 | |
| 	switch reqAuthType {
 | |
| 	case authTypePresignedV2, authTypeSignedV2:
 | |
| 		// Signature V2 validation.
 | |
| 		s3Error := isReqAuthenticatedV2(r)
 | |
| 		if s3Error != ErrNone {
 | |
| 			errorIf(errSignatureMismatch, "%s", dumpRequest(r))
 | |
| 			writeErrorResponse(w, s3Error, r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 	case authTypeSigned, authTypePresigned:
 | |
| 		s3Error := isReqAuthenticated(r, serverConfig.GetRegion())
 | |
| 		if s3Error != ErrNone {
 | |
| 			errorIf(errSignatureMismatch, "%s", dumpRequest(r))
 | |
| 			writeErrorResponse(w, s3Error, r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 	case authTypeAnonymous:
 | |
| 		// No verification needed for anonymous requests.
 | |
| 	default:
 | |
| 		// For all unknown auth types return error.
 | |
| 		writeErrorResponse(w, ErrAccessDenied, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	getBucketInfo := objectAPI.GetBucketInfo
 | |
| 	if reqAuthType == authTypeAnonymous {
 | |
| 		getBucketInfo = objectAPI.AnonGetBucketInfo
 | |
| 	}
 | |
| 
 | |
| 	if _, err := getBucketInfo(bucket); err != nil {
 | |
| 		errorIf(err, "Unable to fetch bucket info.")
 | |
| 		writeErrorResponseHeadersOnly(w, toAPIErrorCode(err))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	writeSuccessResponseHeadersOnly(w)
 | |
| }
 | |
| 
 | |
| // GetBucketLocationHandler - GET Bucket location.
 | |
| // -------------------------
 | |
| // This operation returns bucket location.
 | |
| func (api gatewayAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	vars := router.Vars(r)
 | |
| 	bucket := vars["bucket"]
 | |
| 
 | |
| 	objectAPI := api.ObjectAPI()
 | |
| 	if objectAPI == nil {
 | |
| 		writeErrorResponse(w, ErrServerNotInitialized, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 	reqAuthType := getRequestAuthType(r)
 | |
| 
 | |
| 	switch reqAuthType {
 | |
| 	case authTypePresignedV2, authTypeSignedV2:
 | |
| 		// Signature V2 validation.
 | |
| 		s3Error := isReqAuthenticatedV2(r)
 | |
| 		if s3Error != ErrNone {
 | |
| 			errorIf(errSignatureMismatch, "%s", dumpRequest(r))
 | |
| 			writeErrorResponse(w, s3Error, r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 	case authTypeSigned, authTypePresigned:
 | |
| 		s3Error := isReqAuthenticated(r, globalMinioDefaultRegion)
 | |
| 		if s3Error == ErrInvalidRegion {
 | |
| 			// Clients like boto3 send getBucketLocation() call signed with region that is configured.
 | |
| 			s3Error = isReqAuthenticated(r, serverConfig.GetRegion())
 | |
| 		}
 | |
| 		if s3Error != ErrNone {
 | |
| 			errorIf(errSignatureMismatch, "%s", dumpRequest(r))
 | |
| 			writeErrorResponse(w, s3Error, r.URL)
 | |
| 			return
 | |
| 		}
 | |
| 	case authTypeAnonymous:
 | |
| 		// No verification needed for anonymous requests.
 | |
| 	default:
 | |
| 		// For all unknown auth types return error.
 | |
| 		writeErrorResponse(w, ErrAccessDenied, r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	getBucketInfo := objectAPI.GetBucketInfo
 | |
| 	if reqAuthType == authTypeAnonymous {
 | |
| 		getBucketInfo = objectAPI.AnonGetBucketInfo
 | |
| 	}
 | |
| 
 | |
| 	if _, err := getBucketInfo(bucket); err != nil {
 | |
| 		errorIf(err, "Unable to fetch bucket info.")
 | |
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Generate response.
 | |
| 	encodedSuccessResponse := encodeResponse(LocationResponse{})
 | |
| 	// Get current region.
 | |
| 	region := serverConfig.GetRegion()
 | |
| 	if region != globalMinioDefaultRegion {
 | |
| 		encodedSuccessResponse = encodeResponse(LocationResponse{
 | |
| 			Location: region,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	// Write success response.
 | |
| 	writeSuccessResponseXML(w, encodedSuccessResponse)
 | |
| }
 |