/* * 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" goioutil "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" "github.com/minio/minio/pkg/ioutil" ) // 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, globalServerConfig.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() } getObject := objectAPI.GetObject if reqAuthType == authTypeAnonymous { getObject = objectAPI.AnonGetObject } setObjectHeaders(w, objInfo, hrange) setHeadGetRespHeaders(w, r.URL.Query()) httpWriter := ioutil.WriteOnClose(w) // Reads the object at startOffset and writes to mw. if err = getObject(bucket, object, startOffset, length, httpWriter); err != nil { errorIf(err, "Unable to write to client.") if !httpWriter.HasWritten() { // 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 err = httpWriter.Close(); err != nil { if !httpWriter.HasWritten() { // write error response only if no data has been written to client yet writeErrorResponse(w, toAPIErrorCode(err), r.URL) return } } // 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"] // Validate storage class metadata if present if _, ok := r.Header[amzStorageClassCanonical]; ok { if !isValidStorageClassMeta(r.Header.Get(amzStorageClassCanonical)) { writeErrorResponse(w, ErrInvalidStorageClass, r.URL) return } } // 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, globalServerConfig.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, globalServerConfig.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, "", "", globalServerConfig.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 := goioutil.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, "", "", globalServerConfig.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, "", "", globalServerConfig.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, "", "", globalServerConfig.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, "", "", globalServerConfig.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, globalServerConfig.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, globalServerConfig.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, globalServerConfig.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, globalServerConfig.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 := globalServerConfig.GetRegion() if region != globalMinioDefaultRegion { encodedSuccessResponse = encodeResponse(LocationResponse{ Location: region, }) } // Write success response. writeSuccessResponseXML(w, encodedSuccessResponse) }