diff --git a/api/minio/api-errors.go b/api/minio/api-errors.go
new file mode 100644
index 000000000..c0879ef7c
--- /dev/null
+++ b/api/minio/api-errors.go
@@ -0,0 +1,2235 @@
+// Copyright (c) 2015-2021 MinIO, Inc.
+//
+// This file is part of MinIO Object Storage stack
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package cmd
+
+import (
+ "context"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "github.com/Azure/azure-storage-blob-go/azblob"
+ "google.golang.org/api/googleapi"
+
+ minio "github.com/minio/minio-go/v7"
+ "github.com/minio/minio-go/v7/pkg/tags"
+ "github.com/minio/minio/internal/auth"
+ "github.com/minio/minio/internal/bucket/lifecycle"
+ "github.com/minio/minio/internal/bucket/replication"
+ "github.com/minio/minio/internal/config/dns"
+ "github.com/minio/minio/internal/crypto"
+ "github.com/minio/minio/internal/logger"
+
+ objectlock "github.com/minio/minio/internal/bucket/object/lock"
+ "github.com/minio/minio/internal/bucket/versioning"
+ "github.com/minio/minio/internal/event"
+ "github.com/minio/minio/internal/hash"
+ "github.com/minio/pkg/bucket/policy"
+)
+
+// APIError structure
+type APIError struct {
+ Code string
+ Description string
+ HTTPStatusCode int
+}
+
+// APIErrorResponse - error response format
+type APIErrorResponse struct {
+ XMLName xml.Name `xml:"Error" json:"-"`
+ Code string
+ Message string
+ Key string `xml:"Key,omitempty" json:"Key,omitempty"`
+ BucketName string `xml:"BucketName,omitempty" json:"BucketName,omitempty"`
+ Resource string
+ Region string `xml:"Region,omitempty" json:"Region,omitempty"`
+ RequestID string `xml:"RequestId" json:"RequestId"`
+ HostID string `xml:"HostId" json:"HostId"`
+}
+
+// APIErrorCode type of error status.
+type APIErrorCode int
+
+//go:generate stringer -type=APIErrorCode -trimprefix=Err $GOFILE
+
+// Error codes, non exhaustive list - http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
+const (
+ ErrNone APIErrorCode = iota
+ ErrAccessDenied
+ ErrBadDigest
+ ErrEntityTooSmall
+ ErrEntityTooLarge
+ ErrPolicyTooLarge
+ ErrIncompleteBody
+ ErrInternalError
+ ErrInvalidAccessKeyID
+ ErrInvalidBucketName
+ ErrInvalidDigest
+ ErrInvalidRange
+ ErrInvalidRangePartNumber
+ ErrInvalidCopyPartRange
+ ErrInvalidCopyPartRangeSource
+ ErrInvalidMaxKeys
+ ErrInvalidEncodingMethod
+ ErrInvalidMaxUploads
+ ErrInvalidMaxParts
+ ErrInvalidPartNumberMarker
+ ErrInvalidPartNumber
+ ErrInvalidRequestBody
+ ErrInvalidCopySource
+ ErrInvalidMetadataDirective
+ ErrInvalidCopyDest
+ ErrInvalidPolicyDocument
+ ErrInvalidObjectState
+ ErrMalformedXML
+ ErrMissingContentLength
+ ErrMissingContentMD5
+ ErrMissingRequestBodyError
+ ErrMissingSecurityHeader
+ ErrNoSuchBucket
+ ErrNoSuchBucketPolicy
+ ErrNoSuchBucketLifecycle
+ ErrNoSuchLifecycleConfiguration
+ ErrNoSuchBucketSSEConfig
+ ErrNoSuchCORSConfiguration
+ ErrNoSuchWebsiteConfiguration
+ ErrReplicationConfigurationNotFoundError
+ ErrRemoteDestinationNotFoundError
+ ErrReplicationDestinationMissingLock
+ ErrRemoteTargetNotFoundError
+ ErrReplicationRemoteConnectionError
+ ErrReplicationBandwidthLimitError
+ ErrBucketRemoteIdenticalToSource
+ ErrBucketRemoteAlreadyExists
+ ErrBucketRemoteLabelInUse
+ ErrBucketRemoteArnTypeInvalid
+ ErrBucketRemoteArnInvalid
+ ErrBucketRemoteRemoveDisallowed
+ ErrRemoteTargetNotVersionedError
+ ErrReplicationSourceNotVersionedError
+ ErrReplicationNeedsVersioningError
+ ErrReplicationBucketNeedsVersioningError
+ ErrReplicationNoMatchingRuleError
+ ErrObjectRestoreAlreadyInProgress
+ ErrNoSuchKey
+ ErrNoSuchUpload
+ ErrInvalidVersionID
+ ErrNoSuchVersion
+ ErrNotImplemented
+ ErrPreconditionFailed
+ ErrRequestTimeTooSkewed
+ ErrSignatureDoesNotMatch
+ ErrMethodNotAllowed
+ ErrInvalidPart
+ ErrInvalidPartOrder
+ ErrAuthorizationHeaderMalformed
+ ErrMalformedPOSTRequest
+ ErrPOSTFileRequired
+ ErrSignatureVersionNotSupported
+ ErrBucketNotEmpty
+ ErrAllAccessDisabled
+ ErrMalformedPolicy
+ ErrMissingFields
+ ErrMissingCredTag
+ ErrCredMalformed
+ ErrInvalidRegion
+ ErrInvalidServiceS3
+ ErrInvalidServiceSTS
+ ErrInvalidRequestVersion
+ ErrMissingSignTag
+ ErrMissingSignHeadersTag
+ ErrMalformedDate
+ ErrMalformedPresignedDate
+ ErrMalformedCredentialDate
+ ErrMalformedCredentialRegion
+ ErrMalformedExpires
+ ErrNegativeExpires
+ ErrAuthHeaderEmpty
+ ErrExpiredPresignRequest
+ ErrRequestNotReadyYet
+ ErrUnsignedHeaders
+ ErrMissingDateHeader
+ ErrInvalidQuerySignatureAlgo
+ ErrInvalidQueryParams
+ ErrBucketAlreadyOwnedByYou
+ ErrInvalidDuration
+ ErrBucketAlreadyExists
+ ErrMetadataTooLarge
+ ErrUnsupportedMetadata
+ ErrMaximumExpires
+ ErrSlowDown
+ ErrInvalidPrefixMarker
+ ErrBadRequest
+ ErrKeyTooLongError
+ ErrInvalidBucketObjectLockConfiguration
+ ErrObjectLockConfigurationNotFound
+ ErrObjectLockConfigurationNotAllowed
+ ErrNoSuchObjectLockConfiguration
+ ErrObjectLocked
+ ErrInvalidRetentionDate
+ ErrPastObjectLockRetainDate
+ ErrUnknownWORMModeDirective
+ ErrBucketTaggingNotFound
+ ErrObjectLockInvalidHeaders
+ ErrInvalidTagDirective
+ // Add new error codes here.
+
+ // SSE-S3 related API errors
+ ErrInvalidEncryptionMethod
+
+ // Server-Side-Encryption (with Customer provided key) related API errors.
+ ErrInsecureSSECustomerRequest
+ ErrSSEMultipartEncrypted
+ ErrSSEEncryptedObject
+ ErrInvalidEncryptionParameters
+ ErrInvalidSSECustomerAlgorithm
+ ErrInvalidSSECustomerKey
+ ErrMissingSSECustomerKey
+ ErrMissingSSECustomerKeyMD5
+ ErrSSECustomerKeyMD5Mismatch
+ ErrInvalidSSECustomerParameters
+ ErrIncompatibleEncryptionMethod
+ ErrKMSNotConfigured
+
+ ErrNoAccessKey
+ ErrInvalidToken
+
+ // Bucket notification related errors.
+ ErrEventNotification
+ ErrARNNotification
+ ErrRegionNotification
+ ErrOverlappingFilterNotification
+ ErrFilterNameInvalid
+ ErrFilterNamePrefix
+ ErrFilterNameSuffix
+ ErrFilterValueInvalid
+ ErrOverlappingConfigs
+ ErrUnsupportedNotification
+
+ // S3 extended errors.
+ ErrContentSHA256Mismatch
+
+ // Add new extended error codes here.
+
+ // MinIO extended errors.
+ ErrReadQuorum
+ ErrWriteQuorum
+ ErrStorageFull
+ ErrRequestBodyParse
+ ErrObjectExistsAsDirectory
+ ErrInvalidObjectName
+ ErrInvalidObjectNamePrefixSlash
+ ErrInvalidResourceName
+ ErrServerNotInitialized
+ ErrOperationTimedOut
+ ErrClientDisconnected
+ ErrOperationMaxedOut
+ ErrInvalidRequest
+ ErrTransitionStorageClassNotFoundError
+ // MinIO storage class error codes
+ ErrInvalidStorageClass
+ ErrBackendDown
+ // Add new extended error codes here.
+ // Please open a https://github.com/minio/minio/issues before adding
+ // new error codes here.
+
+ ErrMalformedJSON
+ ErrAdminNoSuchUser
+ ErrAdminNoSuchGroup
+ ErrAdminGroupNotEmpty
+ ErrAdminNoSuchPolicy
+ ErrAdminInvalidArgument
+ ErrAdminInvalidAccessKey
+ ErrAdminInvalidSecretKey
+ ErrAdminConfigNoQuorum
+ ErrAdminConfigTooLarge
+ ErrAdminConfigBadJSON
+ ErrAdminConfigDuplicateKeys
+ ErrAdminCredentialsMismatch
+ ErrInsecureClientRequest
+ ErrObjectTampered
+ // Bucket Quota error codes
+ ErrAdminBucketQuotaExceeded
+ ErrAdminNoSuchQuotaConfiguration
+
+ ErrHealNotImplemented
+ ErrHealNoSuchProcess
+ ErrHealInvalidClientToken
+ ErrHealMissingBucket
+ ErrHealAlreadyRunning
+ ErrHealOverlappingPaths
+ ErrIncorrectContinuationToken
+
+ // S3 Select Errors
+ ErrEmptyRequestBody
+ ErrUnsupportedFunction
+ ErrInvalidExpressionType
+ ErrBusy
+ ErrUnauthorizedAccess
+ ErrExpressionTooLong
+ ErrIllegalSQLFunctionArgument
+ ErrInvalidKeyPath
+ ErrInvalidCompressionFormat
+ ErrInvalidFileHeaderInfo
+ ErrInvalidJSONType
+ ErrInvalidQuoteFields
+ ErrInvalidRequestParameter
+ ErrInvalidDataType
+ ErrInvalidTextEncoding
+ ErrInvalidDataSource
+ ErrInvalidTableAlias
+ ErrMissingRequiredParameter
+ ErrObjectSerializationConflict
+ ErrUnsupportedSQLOperation
+ ErrUnsupportedSQLStructure
+ ErrUnsupportedSyntax
+ ErrUnsupportedRangeHeader
+ ErrLexerInvalidChar
+ ErrLexerInvalidOperator
+ ErrLexerInvalidLiteral
+ ErrLexerInvalidIONLiteral
+ ErrParseExpectedDatePart
+ ErrParseExpectedKeyword
+ ErrParseExpectedTokenType
+ ErrParseExpected2TokenTypes
+ ErrParseExpectedNumber
+ ErrParseExpectedRightParenBuiltinFunctionCall
+ ErrParseExpectedTypeName
+ ErrParseExpectedWhenClause
+ ErrParseUnsupportedToken
+ ErrParseUnsupportedLiteralsGroupBy
+ ErrParseExpectedMember
+ ErrParseUnsupportedSelect
+ ErrParseUnsupportedCase
+ ErrParseUnsupportedCaseClause
+ ErrParseUnsupportedAlias
+ ErrParseUnsupportedSyntax
+ ErrParseUnknownOperator
+ ErrParseMissingIdentAfterAt
+ ErrParseUnexpectedOperator
+ ErrParseUnexpectedTerm
+ ErrParseUnexpectedToken
+ ErrParseUnexpectedKeyword
+ ErrParseExpectedExpression
+ ErrParseExpectedLeftParenAfterCast
+ ErrParseExpectedLeftParenValueConstructor
+ ErrParseExpectedLeftParenBuiltinFunctionCall
+ ErrParseExpectedArgumentDelimiter
+ ErrParseCastArity
+ ErrParseInvalidTypeParam
+ ErrParseEmptySelect
+ ErrParseSelectMissingFrom
+ ErrParseExpectedIdentForGroupName
+ ErrParseExpectedIdentForAlias
+ ErrParseUnsupportedCallWithStar
+ ErrParseNonUnaryAgregateFunctionCall
+ ErrParseMalformedJoin
+ ErrParseExpectedIdentForAt
+ ErrParseAsteriskIsNotAloneInSelectList
+ ErrParseCannotMixSqbAndWildcardInSelectList
+ ErrParseInvalidContextForWildcardInSelectList
+ ErrIncorrectSQLFunctionArgumentType
+ ErrValueParseFailure
+ ErrEvaluatorInvalidArguments
+ ErrIntegerOverflow
+ ErrLikeInvalidInputs
+ ErrCastFailed
+ ErrInvalidCast
+ ErrEvaluatorInvalidTimestampFormatPattern
+ ErrEvaluatorInvalidTimestampFormatPatternSymbolForParsing
+ ErrEvaluatorTimestampFormatPatternDuplicateFields
+ ErrEvaluatorTimestampFormatPatternHourClockAmPmMismatch
+ ErrEvaluatorUnterminatedTimestampFormatPatternToken
+ ErrEvaluatorInvalidTimestampFormatPatternToken
+ ErrEvaluatorInvalidTimestampFormatPatternSymbol
+ ErrEvaluatorBindingDoesNotExist
+ ErrMissingHeaders
+ ErrInvalidColumnIndex
+
+ ErrAdminConfigNotificationTargetsFailed
+ ErrAdminProfilerNotEnabled
+ ErrInvalidDecompressedSize
+ ErrAddUserInvalidArgument
+ ErrAdminAccountNotEligible
+ ErrAccountNotEligible
+ ErrAdminServiceAccountNotFound
+ ErrPostPolicyConditionInvalidFormat
+)
+
+type errorCodeMap map[APIErrorCode]APIError
+
+func (e errorCodeMap) ToAPIErrWithErr(errCode APIErrorCode, err error) APIError {
+ apiErr, ok := e[errCode]
+ if !ok {
+ apiErr = e[ErrInternalError]
+ }
+ if err != nil {
+ apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
+ }
+ if globalServerRegion != "" {
+ switch errCode {
+ case ErrAuthorizationHeaderMalformed:
+ apiErr.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalServerRegion)
+ return apiErr
+ }
+ }
+ return apiErr
+}
+
+func (e errorCodeMap) ToAPIErr(errCode APIErrorCode) APIError {
+ return e.ToAPIErrWithErr(errCode, nil)
+}
+
+// error code to APIError structure, these fields carry respective
+// descriptions for all the error responses.
+var errorCodes = errorCodeMap{
+ ErrInvalidCopyDest: {
+ Code: "InvalidRequest",
+ Description: "This copy request is illegal because it is trying to copy an object to itself without changing the object's metadata, storage class, website redirect location or encryption attributes.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidCopySource: {
+ Code: "InvalidArgument",
+ Description: "Copy Source must mention the source bucket and key: sourcebucket/sourcekey.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidMetadataDirective: {
+ Code: "InvalidArgument",
+ Description: "Unknown metadata directive.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidStorageClass: {
+ Code: "InvalidStorageClass",
+ Description: "Invalid storage class.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidRequestBody: {
+ Code: "InvalidArgument",
+ Description: "Body shouldn't be set for this request.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidMaxUploads: {
+ Code: "InvalidArgument",
+ Description: "Argument max-uploads must be an integer between 0 and 2147483647",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidMaxKeys: {
+ Code: "InvalidArgument",
+ Description: "Argument maxKeys must be an integer between 0 and 2147483647",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidEncodingMethod: {
+ Code: "InvalidArgument",
+ Description: "Invalid Encoding Method specified in Request",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidMaxParts: {
+ Code: "InvalidArgument",
+ Description: "Argument max-parts must be an integer between 0 and 2147483647",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidPartNumberMarker: {
+ Code: "InvalidArgument",
+ Description: "Argument partNumberMarker must be an integer.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidPartNumber: {
+ Code: "InvalidPartNumber",
+ Description: "The requested partnumber is not satisfiable",
+ HTTPStatusCode: http.StatusRequestedRangeNotSatisfiable,
+ },
+ ErrInvalidPolicyDocument: {
+ Code: "InvalidPolicyDocument",
+ Description: "The content of the form does not meet the conditions specified in the policy document.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrAccessDenied: {
+ Code: "AccessDenied",
+ Description: "Access Denied.",
+ HTTPStatusCode: http.StatusForbidden,
+ },
+ ErrBadDigest: {
+ Code: "BadDigest",
+ Description: "The Content-Md5 you specified did not match what we received.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrEntityTooSmall: {
+ Code: "EntityTooSmall",
+ Description: "Your proposed upload is smaller than the minimum allowed object size.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrEntityTooLarge: {
+ Code: "EntityTooLarge",
+ Description: "Your proposed upload exceeds the maximum allowed object size.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrPolicyTooLarge: {
+ Code: "PolicyTooLarge",
+ Description: "Policy exceeds the maximum allowed document size.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrIncompleteBody: {
+ Code: "IncompleteBody",
+ Description: "You did not provide the number of bytes specified by the Content-Length HTTP header.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInternalError: {
+ Code: "InternalError",
+ Description: "We encountered an internal error, please try again.",
+ HTTPStatusCode: http.StatusInternalServerError,
+ },
+ ErrInvalidAccessKeyID: {
+ Code: "InvalidAccessKeyId",
+ Description: "The Access Key Id you provided does not exist in our records.",
+ HTTPStatusCode: http.StatusForbidden,
+ },
+ ErrInvalidBucketName: {
+ Code: "InvalidBucketName",
+ Description: "The specified bucket is not valid.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidDigest: {
+ Code: "InvalidDigest",
+ Description: "The Content-Md5 you specified is not valid.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidRange: {
+ Code: "InvalidRange",
+ Description: "The requested range is not satisfiable",
+ HTTPStatusCode: http.StatusRequestedRangeNotSatisfiable,
+ },
+ ErrInvalidRangePartNumber: {
+ Code: "InvalidRequest",
+ Description: "Cannot specify both Range header and partNumber query parameter",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMalformedXML: {
+ Code: "MalformedXML",
+ Description: "The XML you provided was not well-formed or did not validate against our published schema.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMissingContentLength: {
+ Code: "MissingContentLength",
+ Description: "You must provide the Content-Length HTTP header.",
+ HTTPStatusCode: http.StatusLengthRequired,
+ },
+ ErrMissingContentMD5: {
+ Code: "MissingContentMD5",
+ Description: "Missing required header for this request: Content-Md5.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMissingSecurityHeader: {
+ Code: "MissingSecurityHeader",
+ Description: "Your request was missing a required header",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMissingRequestBodyError: {
+ Code: "MissingRequestBodyError",
+ Description: "Request body is empty.",
+ HTTPStatusCode: http.StatusLengthRequired,
+ },
+ ErrNoSuchBucket: {
+ Code: "NoSuchBucket",
+ Description: "The specified bucket does not exist",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrNoSuchBucketPolicy: {
+ Code: "NoSuchBucketPolicy",
+ Description: "The bucket policy does not exist",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrNoSuchBucketLifecycle: {
+ Code: "NoSuchBucketLifecycle",
+ Description: "The bucket lifecycle configuration does not exist",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrNoSuchLifecycleConfiguration: {
+ Code: "NoSuchLifecycleConfiguration",
+ Description: "The lifecycle configuration does not exist",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrNoSuchBucketSSEConfig: {
+ Code: "ServerSideEncryptionConfigurationNotFoundError",
+ Description: "The server side encryption configuration was not found",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrNoSuchKey: {
+ Code: "NoSuchKey",
+ Description: "The specified key does not exist.",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrNoSuchUpload: {
+ Code: "NoSuchUpload",
+ Description: "The specified multipart upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrInvalidVersionID: {
+ Code: "InvalidArgument",
+ Description: "Invalid version id specified",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrNoSuchVersion: {
+ Code: "NoSuchVersion",
+ Description: "The specified version does not exist.",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrNotImplemented: {
+ Code: "NotImplemented",
+ Description: "A header you provided implies functionality that is not implemented",
+ HTTPStatusCode: http.StatusNotImplemented,
+ },
+ ErrPreconditionFailed: {
+ Code: "PreconditionFailed",
+ Description: "At least one of the pre-conditions you specified did not hold",
+ HTTPStatusCode: http.StatusPreconditionFailed,
+ },
+ ErrRequestTimeTooSkewed: {
+ Code: "RequestTimeTooSkewed",
+ Description: "The difference between the request time and the server's time is too large.",
+ HTTPStatusCode: http.StatusForbidden,
+ },
+ ErrSignatureDoesNotMatch: {
+ Code: "SignatureDoesNotMatch",
+ Description: "The request signature we calculated does not match the signature you provided. Check your key and signing method.",
+ HTTPStatusCode: http.StatusForbidden,
+ },
+ ErrMethodNotAllowed: {
+ Code: "MethodNotAllowed",
+ Description: "The specified method is not allowed against this resource.",
+ HTTPStatusCode: http.StatusMethodNotAllowed,
+ },
+ ErrInvalidPart: {
+ Code: "InvalidPart",
+ Description: "One or more of the specified parts could not be found. The part may not have been uploaded, or the specified entity tag may not match the part's entity tag.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidPartOrder: {
+ Code: "InvalidPartOrder",
+ Description: "The list of parts was not in ascending order. The parts list must be specified in order by part number.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidObjectState: {
+ Code: "InvalidObjectState",
+ Description: "The operation is not valid for the current state of the object.",
+ HTTPStatusCode: http.StatusForbidden,
+ },
+ ErrAuthorizationHeaderMalformed: {
+ Code: "AuthorizationHeaderMalformed",
+ Description: "The authorization header is malformed; the region is wrong; expecting 'us-east-1'.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMalformedPOSTRequest: {
+ Code: "MalformedPOSTRequest",
+ Description: "The body of your POST request is not well-formed multipart/form-data.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrPOSTFileRequired: {
+ Code: "InvalidArgument",
+ Description: "POST requires exactly one file upload per request.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrSignatureVersionNotSupported: {
+ Code: "InvalidRequest",
+ Description: "The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrBucketNotEmpty: {
+ Code: "BucketNotEmpty",
+ Description: "The bucket you tried to delete is not empty",
+ HTTPStatusCode: http.StatusConflict,
+ },
+ ErrBucketAlreadyExists: {
+ Code: "BucketAlreadyExists",
+ Description: "The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again.",
+ HTTPStatusCode: http.StatusConflict,
+ },
+ ErrAllAccessDisabled: {
+ Code: "AllAccessDisabled",
+ Description: "All access to this bucket has been disabled.",
+ HTTPStatusCode: http.StatusForbidden,
+ },
+ ErrMalformedPolicy: {
+ Code: "MalformedPolicy",
+ Description: "Policy has invalid resource.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMissingFields: {
+ Code: "MissingFields",
+ Description: "Missing fields in request.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMissingCredTag: {
+ Code: "InvalidRequest",
+ Description: "Missing Credential field for this request.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrCredMalformed: {
+ Code: "AuthorizationQueryParametersError",
+ Description: "Error parsing the X-Amz-Credential parameter; the Credential is mal-formed; expecting \"/YYYYMMDD/REGION/SERVICE/aws4_request\".",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMalformedDate: {
+ Code: "MalformedDate",
+ Description: "Invalid date format header, expected to be in ISO8601, RFC1123 or RFC1123Z time format.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMalformedPresignedDate: {
+ Code: "AuthorizationQueryParametersError",
+ Description: "X-Amz-Date must be in the ISO8601 Long Format \"yyyyMMdd'T'HHmmss'Z'\"",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMalformedCredentialDate: {
+ Code: "AuthorizationQueryParametersError",
+ Description: "Error parsing the X-Amz-Credential parameter; incorrect date format. This date in the credential must be in the format \"yyyyMMdd\".",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidRegion: {
+ Code: "InvalidRegion",
+ Description: "Region does not match.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidServiceS3: {
+ Code: "AuthorizationParametersError",
+ Description: "Error parsing the Credential/X-Amz-Credential parameter; incorrect service. This endpoint belongs to \"s3\".",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidServiceSTS: {
+ Code: "AuthorizationParametersError",
+ Description: "Error parsing the Credential parameter; incorrect service. This endpoint belongs to \"sts\".",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidRequestVersion: {
+ Code: "AuthorizationQueryParametersError",
+ Description: "Error parsing the X-Amz-Credential parameter; incorrect terminal. This endpoint uses \"aws4_request\".",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMissingSignTag: {
+ Code: "AccessDenied",
+ Description: "Signature header missing Signature field.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMissingSignHeadersTag: {
+ Code: "InvalidArgument",
+ Description: "Signature header missing SignedHeaders field.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMalformedExpires: {
+ Code: "AuthorizationQueryParametersError",
+ Description: "X-Amz-Expires should be a number",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrNegativeExpires: {
+ Code: "AuthorizationQueryParametersError",
+ Description: "X-Amz-Expires must be non-negative",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrAuthHeaderEmpty: {
+ Code: "InvalidArgument",
+ Description: "Authorization header is invalid -- one and only one ' ' (space) required.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMissingDateHeader: {
+ Code: "AccessDenied",
+ Description: "AWS authentication requires a valid Date or x-amz-date header",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidQuerySignatureAlgo: {
+ Code: "AuthorizationQueryParametersError",
+ Description: "X-Amz-Algorithm only supports \"AWS4-HMAC-SHA256\".",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrExpiredPresignRequest: {
+ Code: "AccessDenied",
+ Description: "Request has expired",
+ HTTPStatusCode: http.StatusForbidden,
+ },
+ ErrRequestNotReadyYet: {
+ Code: "AccessDenied",
+ Description: "Request is not valid yet",
+ HTTPStatusCode: http.StatusForbidden,
+ },
+ ErrSlowDown: {
+ Code: "SlowDown",
+ Description: "Resource requested is unreadable, please reduce your request rate",
+ HTTPStatusCode: http.StatusServiceUnavailable,
+ },
+ ErrInvalidPrefixMarker: {
+ Code: "InvalidPrefixMarker",
+ Description: "Invalid marker prefix combination",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrBadRequest: {
+ Code: "BadRequest",
+ Description: "400 BadRequest",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrKeyTooLongError: {
+ Code: "KeyTooLongError",
+ Description: "Your key is too long",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrUnsignedHeaders: {
+ Code: "AccessDenied",
+ Description: "There were headers present in the request which were not signed",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidQueryParams: {
+ Code: "AuthorizationQueryParametersError",
+ Description: "Query-string authentication version 4 requires the X-Amz-Algorithm, X-Amz-Credential, X-Amz-Signature, X-Amz-Date, X-Amz-SignedHeaders, and X-Amz-Expires parameters.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrBucketAlreadyOwnedByYou: {
+ Code: "BucketAlreadyOwnedByYou",
+ Description: "Your previous request to create the named bucket succeeded and you already own it.",
+ HTTPStatusCode: http.StatusConflict,
+ },
+ ErrInvalidDuration: {
+ Code: "InvalidDuration",
+ Description: "Duration provided in the request is invalid.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidBucketObjectLockConfiguration: {
+ Code: "InvalidRequest",
+ Description: "Bucket is missing ObjectLockConfiguration",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrBucketTaggingNotFound: {
+ Code: "NoSuchTagSet",
+ Description: "The TagSet does not exist",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrObjectLockConfigurationNotFound: {
+ Code: "ObjectLockConfigurationNotFoundError",
+ Description: "Object Lock configuration does not exist for this bucket",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrObjectLockConfigurationNotAllowed: {
+ Code: "InvalidBucketState",
+ Description: "Object Lock configuration cannot be enabled on existing buckets",
+ HTTPStatusCode: http.StatusConflict,
+ },
+ ErrNoSuchCORSConfiguration: {
+ Code: "NoSuchCORSConfiguration",
+ Description: "The CORS configuration does not exist",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrNoSuchWebsiteConfiguration: {
+ Code: "NoSuchWebsiteConfiguration",
+ Description: "The specified bucket does not have a website configuration",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrReplicationConfigurationNotFoundError: {
+ Code: "ReplicationConfigurationNotFoundError",
+ Description: "The replication configuration was not found",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrRemoteDestinationNotFoundError: {
+ Code: "RemoteDestinationNotFoundError",
+ Description: "The remote destination bucket does not exist",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrReplicationDestinationMissingLock: {
+ Code: "ReplicationDestinationMissingLockError",
+ Description: "The replication destination bucket does not have object locking enabled",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrRemoteTargetNotFoundError: {
+ Code: "XMinioAdminRemoteTargetNotFoundError",
+ Description: "The remote target does not exist",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrReplicationRemoteConnectionError: {
+ Code: "XMinioAdminReplicationRemoteConnectionError",
+ Description: "Remote service connection error - please check remote service credentials and target bucket",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrReplicationBandwidthLimitError: {
+ Code: "XMinioAdminReplicationBandwidthLimitError",
+ Description: "Bandwidth limit for remote target must be atleast 100MBps",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrReplicationNoMatchingRuleError: {
+ Code: "XMinioReplicationNoMatchingRule",
+ Description: "No matching replication rule found for this object prefix",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrBucketRemoteIdenticalToSource: {
+ Code: "XMinioAdminRemoteIdenticalToSource",
+ Description: "The remote target cannot be identical to source",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrBucketRemoteAlreadyExists: {
+ Code: "XMinioAdminBucketRemoteAlreadyExists",
+ Description: "The remote target already exists",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrBucketRemoteLabelInUse: {
+ Code: "XMinioAdminBucketRemoteLabelInUse",
+ Description: "The remote target with this label already exists",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrBucketRemoteRemoveDisallowed: {
+ Code: "XMinioAdminRemoteRemoveDisallowed",
+ Description: "This ARN is in use by an existing configuration",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrBucketRemoteArnTypeInvalid: {
+ Code: "XMinioAdminRemoteARNTypeInvalid",
+ Description: "The bucket remote ARN type is not valid",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrBucketRemoteArnInvalid: {
+ Code: "XMinioAdminRemoteArnInvalid",
+ Description: "The bucket remote ARN does not have correct format",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrRemoteTargetNotVersionedError: {
+ Code: "RemoteTargetNotVersionedError",
+ Description: "The remote target does not have versioning enabled",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrReplicationSourceNotVersionedError: {
+ Code: "ReplicationSourceNotVersionedError",
+ Description: "The replication source does not have versioning enabled",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrReplicationNeedsVersioningError: {
+ Code: "InvalidRequest",
+ Description: "Versioning must be 'Enabled' on the bucket to apply a replication configuration",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrReplicationBucketNeedsVersioningError: {
+ Code: "InvalidRequest",
+ Description: "Versioning must be 'Enabled' on the bucket to add a replication target",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrNoSuchObjectLockConfiguration: {
+ Code: "NoSuchObjectLockConfiguration",
+ Description: "The specified object does not have a ObjectLock configuration",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrObjectLocked: {
+ Code: "InvalidRequest",
+ Description: "Object is WORM protected and cannot be overwritten",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidRetentionDate: {
+ Code: "InvalidRequest",
+ Description: "Date must be provided in ISO 8601 format",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrPastObjectLockRetainDate: {
+ Code: "InvalidRequest",
+ Description: "the retain until date must be in the future",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrUnknownWORMModeDirective: {
+ Code: "InvalidRequest",
+ Description: "unknown wormMode directive",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrObjectLockInvalidHeaders: {
+ Code: "InvalidRequest",
+ Description: "x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrObjectRestoreAlreadyInProgress: {
+ Code: "RestoreAlreadyInProgress",
+ Description: "Object restore is already in progress",
+ HTTPStatusCode: http.StatusConflict,
+ },
+ ErrTransitionStorageClassNotFoundError: {
+ Code: "TransitionStorageClassNotFoundError",
+ Description: "The transition storage class was not found",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+
+ /// Bucket notification related errors.
+ ErrEventNotification: {
+ Code: "InvalidArgument",
+ Description: "A specified event is not supported for notifications.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrARNNotification: {
+ Code: "InvalidArgument",
+ Description: "A specified destination ARN does not exist or is not well-formed. Verify the destination ARN.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrRegionNotification: {
+ Code: "InvalidArgument",
+ Description: "A specified destination is in a different region than the bucket. You must use a destination that resides in the same region as the bucket.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrOverlappingFilterNotification: {
+ Code: "InvalidArgument",
+ Description: "An object key name filtering rule defined with overlapping prefixes, overlapping suffixes, or overlapping combinations of prefixes and suffixes for the same event types.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrFilterNameInvalid: {
+ Code: "InvalidArgument",
+ Description: "filter rule name must be either prefix or suffix",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrFilterNamePrefix: {
+ Code: "InvalidArgument",
+ Description: "Cannot specify more than one prefix rule in a filter.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrFilterNameSuffix: {
+ Code: "InvalidArgument",
+ Description: "Cannot specify more than one suffix rule in a filter.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrFilterValueInvalid: {
+ Code: "InvalidArgument",
+ Description: "Size of filter rule value cannot exceed 1024 bytes in UTF-8 representation",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrOverlappingConfigs: {
+ Code: "InvalidArgument",
+ Description: "Configurations overlap. Configurations on the same bucket cannot share a common event type.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrUnsupportedNotification: {
+ Code: "UnsupportedNotification",
+ Description: "MinIO server does not support Topic or Cloud Function based notifications.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidCopyPartRange: {
+ Code: "InvalidArgument",
+ Description: "The x-amz-copy-source-range value must be of the form bytes=first-last where first and last are the zero-based offsets of the first and last bytes to copy",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidCopyPartRangeSource: {
+ Code: "InvalidArgument",
+ Description: "Range specified is not valid for source object",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMetadataTooLarge: {
+ Code: "MetadataTooLarge",
+ Description: "Your metadata headers exceed the maximum allowed metadata size.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidTagDirective: {
+ Code: "InvalidArgument",
+ Description: "Unknown tag directive.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidEncryptionMethod: {
+ Code: "InvalidRequest",
+ Description: "The encryption method specified is not supported",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInsecureSSECustomerRequest: {
+ Code: "InvalidRequest",
+ Description: "Requests specifying Server Side Encryption with Customer provided keys must be made over a secure connection.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrSSEMultipartEncrypted: {
+ Code: "InvalidRequest",
+ Description: "The multipart upload initiate requested encryption. Subsequent part requests must include the appropriate encryption parameters.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrSSEEncryptedObject: {
+ Code: "InvalidRequest",
+ Description: "The object was stored using a form of Server Side Encryption. The correct parameters must be provided to retrieve the object.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidEncryptionParameters: {
+ Code: "InvalidRequest",
+ Description: "The encryption parameters are not applicable to this object.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidSSECustomerAlgorithm: {
+ Code: "InvalidArgument",
+ Description: "Requests specifying Server Side Encryption with Customer provided keys must provide a valid encryption algorithm.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidSSECustomerKey: {
+ Code: "InvalidArgument",
+ Description: "The secret key was invalid for the specified algorithm.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMissingSSECustomerKey: {
+ Code: "InvalidArgument",
+ Description: "Requests specifying Server Side Encryption with Customer provided keys must provide an appropriate secret key.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMissingSSECustomerKeyMD5: {
+ Code: "InvalidArgument",
+ Description: "Requests specifying Server Side Encryption with Customer provided keys must provide the client calculated MD5 of the secret key.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrSSECustomerKeyMD5Mismatch: {
+ Code: "InvalidArgument",
+ Description: "The calculated MD5 hash of the key did not match the hash that was provided.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidSSECustomerParameters: {
+ Code: "InvalidArgument",
+ Description: "The provided encryption parameters did not match the ones used originally.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrIncompatibleEncryptionMethod: {
+ Code: "InvalidArgument",
+ Description: "Server side encryption specified with both SSE-C and SSE-S3 headers",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrKMSNotConfigured: {
+ Code: "NotImplemented",
+ Description: "Server side encryption specified but KMS is not configured",
+ HTTPStatusCode: http.StatusNotImplemented,
+ },
+ ErrNoAccessKey: {
+ Code: "AccessDenied",
+ Description: "No AWSAccessKey was presented",
+ HTTPStatusCode: http.StatusForbidden,
+ },
+ ErrInvalidToken: {
+ Code: "InvalidTokenId",
+ Description: "The security token included in the request is invalid",
+ HTTPStatusCode: http.StatusForbidden,
+ },
+
+ /// S3 extensions.
+ ErrContentSHA256Mismatch: {
+ Code: "XAmzContentSHA256Mismatch",
+ Description: "The provided 'x-amz-content-sha256' header does not match what was computed.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+
+ /// MinIO extensions.
+ ErrStorageFull: {
+ Code: "XMinioStorageFull",
+ Description: "Storage backend has reached its minimum free disk threshold. Please delete a few objects to proceed.",
+ HTTPStatusCode: http.StatusInsufficientStorage,
+ },
+ ErrRequestBodyParse: {
+ Code: "XMinioRequestBodyParse",
+ Description: "The request body failed to parse.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrObjectExistsAsDirectory: {
+ Code: "XMinioObjectExistsAsDirectory",
+ Description: "Object name already exists as a directory.",
+ HTTPStatusCode: http.StatusConflict,
+ },
+ ErrInvalidObjectName: {
+ Code: "XMinioInvalidObjectName",
+ Description: "Object name contains unsupported characters.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidObjectNamePrefixSlash: {
+ Code: "XMinioInvalidObjectName",
+ Description: "Object name contains a leading slash.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidResourceName: {
+ Code: "XMinioInvalidResourceName",
+ Description: "Resource name contains bad components such as \"..\" or \".\".",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrServerNotInitialized: {
+ Code: "XMinioServerNotInitialized",
+ Description: "Server not initialized, please try again.",
+ HTTPStatusCode: http.StatusServiceUnavailable,
+ },
+ ErrMalformedJSON: {
+ Code: "XMinioMalformedJSON",
+ Description: "The JSON you provided was not well-formed or did not validate against our published format.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrAdminNoSuchUser: {
+ Code: "XMinioAdminNoSuchUser",
+ Description: "The specified user does not exist.",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrAdminNoSuchGroup: {
+ Code: "XMinioAdminNoSuchGroup",
+ Description: "The specified group does not exist.",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrAdminGroupNotEmpty: {
+ Code: "XMinioAdminGroupNotEmpty",
+ Description: "The specified group is not empty - cannot remove it.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrAdminNoSuchPolicy: {
+ Code: "XMinioAdminNoSuchPolicy",
+ Description: "The canned policy does not exist.",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrAdminInvalidArgument: {
+ Code: "XMinioAdminInvalidArgument",
+ Description: "Invalid arguments specified.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrAdminInvalidAccessKey: {
+ Code: "XMinioAdminInvalidAccessKey",
+ Description: "The access key is invalid.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrAdminInvalidSecretKey: {
+ Code: "XMinioAdminInvalidSecretKey",
+ Description: "The secret key is invalid.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrAdminConfigNoQuorum: {
+ Code: "XMinioAdminConfigNoQuorum",
+ Description: "Configuration update failed because server quorum was not met",
+ HTTPStatusCode: http.StatusServiceUnavailable,
+ },
+ ErrAdminConfigTooLarge: {
+ Code: "XMinioAdminConfigTooLarge",
+ Description: fmt.Sprintf("Configuration data provided exceeds the allowed maximum of %d bytes",
+ maxEConfigJSONSize),
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrAdminConfigBadJSON: {
+ Code: "XMinioAdminConfigBadJSON",
+ Description: "JSON configuration provided is of incorrect format",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrAdminConfigDuplicateKeys: {
+ Code: "XMinioAdminConfigDuplicateKeys",
+ Description: "JSON configuration provided has objects with duplicate keys",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrAdminConfigNotificationTargetsFailed: {
+ Code: "XMinioAdminNotificationTargetsTestFailed",
+ Description: "Configuration update failed due an unsuccessful attempt to connect to one or more notification servers",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrAdminProfilerNotEnabled: {
+ Code: "XMinioAdminProfilerNotEnabled",
+ Description: "Unable to perform the requested operation because profiling is not enabled",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrAdminCredentialsMismatch: {
+ Code: "XMinioAdminCredentialsMismatch",
+ Description: "Credentials in config mismatch with server environment variables",
+ HTTPStatusCode: http.StatusServiceUnavailable,
+ },
+ ErrAdminBucketQuotaExceeded: {
+ Code: "XMinioAdminBucketQuotaExceeded",
+ Description: "Bucket quota exceeded",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrAdminNoSuchQuotaConfiguration: {
+ Code: "XMinioAdminNoSuchQuotaConfiguration",
+ Description: "The quota configuration does not exist",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrInsecureClientRequest: {
+ Code: "XMinioInsecureClientRequest",
+ Description: "Cannot respond to plain-text request from TLS-encrypted server",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrOperationTimedOut: {
+ Code: "RequestTimeout",
+ Description: "A timeout occurred while trying to lock a resource, please reduce your request rate",
+ HTTPStatusCode: http.StatusServiceUnavailable,
+ },
+ ErrClientDisconnected: {
+ Code: "ClientDisconnected",
+ Description: "Client disconnected before response was ready",
+ HTTPStatusCode: 499, // No official code, use nginx value.
+ },
+ ErrOperationMaxedOut: {
+ Code: "SlowDown",
+ Description: "A timeout exceeded while waiting to proceed with the request, please reduce your request rate",
+ HTTPStatusCode: http.StatusServiceUnavailable,
+ },
+ ErrUnsupportedMetadata: {
+ Code: "InvalidArgument",
+ Description: "Your metadata headers are not supported.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrObjectTampered: {
+ Code: "XMinioObjectTampered",
+ Description: errObjectTampered.Error(),
+ HTTPStatusCode: http.StatusPartialContent,
+ },
+ ErrMaximumExpires: {
+ Code: "AuthorizationQueryParametersError",
+ Description: "X-Amz-Expires must be less than a week (in seconds); that is, the given X-Amz-Expires must be less than 604800 seconds",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+
+ // Generic Invalid-Request error. Should be used for response errors only for unlikely
+ // corner case errors for which introducing new APIErrorCode is not worth it. LogIf()
+ // should be used to log the error at the source of the error for debugging purposes.
+ ErrInvalidRequest: {
+ Code: "InvalidRequest",
+ Description: "Invalid Request",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrHealNotImplemented: {
+ Code: "XMinioHealNotImplemented",
+ Description: "This server does not implement heal functionality.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrHealNoSuchProcess: {
+ Code: "XMinioHealNoSuchProcess",
+ Description: "No such heal process is running on the server",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrHealInvalidClientToken: {
+ Code: "XMinioHealInvalidClientToken",
+ Description: "Client token mismatch",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrHealMissingBucket: {
+ Code: "XMinioHealMissingBucket",
+ Description: "A heal start request with a non-empty object-prefix parameter requires a bucket to be specified.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrHealAlreadyRunning: {
+ Code: "XMinioHealAlreadyRunning",
+ Description: "",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrHealOverlappingPaths: {
+ Code: "XMinioHealOverlappingPaths",
+ Description: "",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrBackendDown: {
+ Code: "XMinioBackendDown",
+ Description: "Object storage backend is unreachable",
+ HTTPStatusCode: http.StatusServiceUnavailable,
+ },
+ ErrIncorrectContinuationToken: {
+ Code: "InvalidArgument",
+ Description: "The continuation token provided is incorrect",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ //S3 Select API Errors
+ ErrEmptyRequestBody: {
+ Code: "EmptyRequestBody",
+ Description: "Request body cannot be empty.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrUnsupportedFunction: {
+ Code: "UnsupportedFunction",
+ Description: "Encountered an unsupported SQL function.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidDataSource: {
+ Code: "InvalidDataSource",
+ Description: "Invalid data source type. Only CSV and JSON are supported at this time.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidExpressionType: {
+ Code: "InvalidExpressionType",
+ Description: "The ExpressionType is invalid. Only SQL expressions are supported at this time.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrBusy: {
+ Code: "Busy",
+ Description: "The service is unavailable. Please retry.",
+ HTTPStatusCode: http.StatusServiceUnavailable,
+ },
+ ErrUnauthorizedAccess: {
+ Code: "UnauthorizedAccess",
+ Description: "You are not authorized to perform this operation",
+ HTTPStatusCode: http.StatusUnauthorized,
+ },
+ ErrExpressionTooLong: {
+ Code: "ExpressionTooLong",
+ Description: "The SQL expression is too long: The maximum byte-length for the SQL expression is 256 KB.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrIllegalSQLFunctionArgument: {
+ Code: "IllegalSqlFunctionArgument",
+ Description: "Illegal argument was used in the SQL function.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidKeyPath: {
+ Code: "InvalidKeyPath",
+ Description: "Key path in the SQL expression is invalid.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidCompressionFormat: {
+ Code: "InvalidCompressionFormat",
+ Description: "The file is not in a supported compression format. Only GZIP is supported at this time.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidFileHeaderInfo: {
+ Code: "InvalidFileHeaderInfo",
+ Description: "The FileHeaderInfo is invalid. Only NONE, USE, and IGNORE are supported.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidJSONType: {
+ Code: "InvalidJsonType",
+ Description: "The JsonType is invalid. Only DOCUMENT and LINES are supported at this time.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidQuoteFields: {
+ Code: "InvalidQuoteFields",
+ Description: "The QuoteFields is invalid. Only ALWAYS and ASNEEDED are supported.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidRequestParameter: {
+ Code: "InvalidRequestParameter",
+ Description: "The value of a parameter in SelectRequest element is invalid. Check the service API documentation and try again.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidDataType: {
+ Code: "InvalidDataType",
+ Description: "The SQL expression contains an invalid data type.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidTextEncoding: {
+ Code: "InvalidTextEncoding",
+ Description: "Invalid encoding type. Only UTF-8 encoding is supported at this time.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidTableAlias: {
+ Code: "InvalidTableAlias",
+ Description: "The SQL expression contains an invalid table alias.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMissingRequiredParameter: {
+ Code: "MissingRequiredParameter",
+ Description: "The SelectRequest entity is missing a required parameter. Check the service documentation and try again.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrObjectSerializationConflict: {
+ Code: "ObjectSerializationConflict",
+ Description: "The SelectRequest entity can only contain one of CSV or JSON. Check the service documentation and try again.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrUnsupportedSQLOperation: {
+ Code: "UnsupportedSqlOperation",
+ Description: "Encountered an unsupported SQL operation.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrUnsupportedSQLStructure: {
+ Code: "UnsupportedSqlStructure",
+ Description: "Encountered an unsupported SQL structure. Check the SQL Reference.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrUnsupportedSyntax: {
+ Code: "UnsupportedSyntax",
+ Description: "Encountered invalid syntax.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrUnsupportedRangeHeader: {
+ Code: "UnsupportedRangeHeader",
+ Description: "Range header is not supported for this operation.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrLexerInvalidChar: {
+ Code: "LexerInvalidChar",
+ Description: "The SQL expression contains an invalid character.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrLexerInvalidOperator: {
+ Code: "LexerInvalidOperator",
+ Description: "The SQL expression contains an invalid literal.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrLexerInvalidLiteral: {
+ Code: "LexerInvalidLiteral",
+ Description: "The SQL expression contains an invalid operator.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrLexerInvalidIONLiteral: {
+ Code: "LexerInvalidIONLiteral",
+ Description: "The SQL expression contains an invalid operator.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedDatePart: {
+ Code: "ParseExpectedDatePart",
+ Description: "Did not find the expected date part in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedKeyword: {
+ Code: "ParseExpectedKeyword",
+ Description: "Did not find the expected keyword in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedTokenType: {
+ Code: "ParseExpectedTokenType",
+ Description: "Did not find the expected token in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpected2TokenTypes: {
+ Code: "ParseExpected2TokenTypes",
+ Description: "Did not find the expected token in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedNumber: {
+ Code: "ParseExpectedNumber",
+ Description: "Did not find the expected number in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedRightParenBuiltinFunctionCall: {
+ Code: "ParseExpectedRightParenBuiltinFunctionCall",
+ Description: "Did not find the expected right parenthesis character in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedTypeName: {
+ Code: "ParseExpectedTypeName",
+ Description: "Did not find the expected type name in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedWhenClause: {
+ Code: "ParseExpectedWhenClause",
+ Description: "Did not find the expected WHEN clause in the SQL expression. CASE is not supported.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseUnsupportedToken: {
+ Code: "ParseUnsupportedToken",
+ Description: "The SQL expression contains an unsupported token.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseUnsupportedLiteralsGroupBy: {
+ Code: "ParseUnsupportedLiteralsGroupBy",
+ Description: "The SQL expression contains an unsupported use of GROUP BY.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedMember: {
+ Code: "ParseExpectedMember",
+ Description: "The SQL expression contains an unsupported use of MEMBER.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseUnsupportedSelect: {
+ Code: "ParseUnsupportedSelect",
+ Description: "The SQL expression contains an unsupported use of SELECT.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseUnsupportedCase: {
+ Code: "ParseUnsupportedCase",
+ Description: "The SQL expression contains an unsupported use of CASE.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseUnsupportedCaseClause: {
+ Code: "ParseUnsupportedCaseClause",
+ Description: "The SQL expression contains an unsupported use of CASE.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseUnsupportedAlias: {
+ Code: "ParseUnsupportedAlias",
+ Description: "The SQL expression contains an unsupported use of ALIAS.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseUnsupportedSyntax: {
+ Code: "ParseUnsupportedSyntax",
+ Description: "The SQL expression contains unsupported syntax.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseUnknownOperator: {
+ Code: "ParseUnknownOperator",
+ Description: "The SQL expression contains an invalid operator.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseMissingIdentAfterAt: {
+ Code: "ParseMissingIdentAfterAt",
+ Description: "Did not find the expected identifier after the @ symbol in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseUnexpectedOperator: {
+ Code: "ParseUnexpectedOperator",
+ Description: "The SQL expression contains an unexpected operator.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseUnexpectedTerm: {
+ Code: "ParseUnexpectedTerm",
+ Description: "The SQL expression contains an unexpected term.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseUnexpectedToken: {
+ Code: "ParseUnexpectedToken",
+ Description: "The SQL expression contains an unexpected token.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseUnexpectedKeyword: {
+ Code: "ParseUnexpectedKeyword",
+ Description: "The SQL expression contains an unexpected keyword.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedExpression: {
+ Code: "ParseExpectedExpression",
+ Description: "Did not find the expected SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedLeftParenAfterCast: {
+ Code: "ParseExpectedLeftParenAfterCast",
+ Description: "Did not find expected the left parenthesis in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedLeftParenValueConstructor: {
+ Code: "ParseExpectedLeftParenValueConstructor",
+ Description: "Did not find expected the left parenthesis in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedLeftParenBuiltinFunctionCall: {
+ Code: "ParseExpectedLeftParenBuiltinFunctionCall",
+ Description: "Did not find the expected left parenthesis in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedArgumentDelimiter: {
+ Code: "ParseExpectedArgumentDelimiter",
+ Description: "Did not find the expected argument delimiter in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseCastArity: {
+ Code: "ParseCastArity",
+ Description: "The SQL expression CAST has incorrect arity.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseInvalidTypeParam: {
+ Code: "ParseInvalidTypeParam",
+ Description: "The SQL expression contains an invalid parameter value.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseEmptySelect: {
+ Code: "ParseEmptySelect",
+ Description: "The SQL expression contains an empty SELECT.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseSelectMissingFrom: {
+ Code: "ParseSelectMissingFrom",
+ Description: "GROUP is not supported in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedIdentForGroupName: {
+ Code: "ParseExpectedIdentForGroupName",
+ Description: "GROUP is not supported in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedIdentForAlias: {
+ Code: "ParseExpectedIdentForAlias",
+ Description: "Did not find the expected identifier for the alias in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseUnsupportedCallWithStar: {
+ Code: "ParseUnsupportedCallWithStar",
+ Description: "Only COUNT with (*) as a parameter is supported in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseNonUnaryAgregateFunctionCall: {
+ Code: "ParseNonUnaryAgregateFunctionCall",
+ Description: "Only one argument is supported for aggregate functions in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseMalformedJoin: {
+ Code: "ParseMalformedJoin",
+ Description: "JOIN is not supported in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseExpectedIdentForAt: {
+ Code: "ParseExpectedIdentForAt",
+ Description: "Did not find the expected identifier for AT name in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseAsteriskIsNotAloneInSelectList: {
+ Code: "ParseAsteriskIsNotAloneInSelectList",
+ Description: "Other expressions are not allowed in the SELECT list when '*' is used without dot notation in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseCannotMixSqbAndWildcardInSelectList: {
+ Code: "ParseCannotMixSqbAndWildcardInSelectList",
+ Description: "Cannot mix [] and * in the same expression in a SELECT list in SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrParseInvalidContextForWildcardInSelectList: {
+ Code: "ParseInvalidContextForWildcardInSelectList",
+ Description: "Invalid use of * in SELECT list in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrIncorrectSQLFunctionArgumentType: {
+ Code: "IncorrectSqlFunctionArgumentType",
+ Description: "Incorrect type of arguments in function call in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrValueParseFailure: {
+ Code: "ValueParseFailure",
+ Description: "Time stamp parse failure in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrEvaluatorInvalidArguments: {
+ Code: "EvaluatorInvalidArguments",
+ Description: "Incorrect number of arguments in the function call in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrIntegerOverflow: {
+ Code: "IntegerOverflow",
+ Description: "Int overflow or underflow in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrLikeInvalidInputs: {
+ Code: "LikeInvalidInputs",
+ Description: "Invalid argument given to the LIKE clause in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrCastFailed: {
+ Code: "CastFailed",
+ Description: "Attempt to convert from one data type to another using CAST failed in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidCast: {
+ Code: "InvalidCast",
+ Description: "Attempt to convert from one data type to another using CAST failed in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrEvaluatorInvalidTimestampFormatPattern: {
+ Code: "EvaluatorInvalidTimestampFormatPattern",
+ Description: "Time stamp format pattern requires additional fields in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrEvaluatorInvalidTimestampFormatPatternSymbolForParsing: {
+ Code: "EvaluatorInvalidTimestampFormatPatternSymbolForParsing",
+ Description: "Time stamp format pattern contains a valid format symbol that cannot be applied to time stamp parsing in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrEvaluatorTimestampFormatPatternDuplicateFields: {
+ Code: "EvaluatorTimestampFormatPatternDuplicateFields",
+ Description: "Time stamp format pattern contains multiple format specifiers representing the time stamp field in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrEvaluatorTimestampFormatPatternHourClockAmPmMismatch: {
+ Code: "EvaluatorUnterminatedTimestampFormatPatternToken",
+ Description: "Time stamp format pattern contains unterminated token in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrEvaluatorUnterminatedTimestampFormatPatternToken: {
+ Code: "EvaluatorInvalidTimestampFormatPatternToken",
+ Description: "Time stamp format pattern contains an invalid token in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrEvaluatorInvalidTimestampFormatPatternToken: {
+ Code: "EvaluatorInvalidTimestampFormatPatternToken",
+ Description: "Time stamp format pattern contains an invalid token in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrEvaluatorInvalidTimestampFormatPatternSymbol: {
+ Code: "EvaluatorInvalidTimestampFormatPatternSymbol",
+ Description: "Time stamp format pattern contains an invalid symbol in the SQL expression.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrEvaluatorBindingDoesNotExist: {
+ Code: "ErrEvaluatorBindingDoesNotExist",
+ Description: "A column name or a path provided does not exist in the SQL expression",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrMissingHeaders: {
+ Code: "MissingHeaders",
+ Description: "Some headers in the query are missing from the file. Check the file and try again.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidColumnIndex: {
+ Code: "InvalidColumnIndex",
+ Description: "The column index is invalid. Please check the service documentation and try again.",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrInvalidDecompressedSize: {
+ Code: "XMinioInvalidDecompressedSize",
+ Description: "The data provided is unfit for decompression",
+ HTTPStatusCode: http.StatusBadRequest,
+ },
+ ErrAddUserInvalidArgument: {
+ Code: "XMinioInvalidIAMCredentials",
+ Description: "User is not allowed to be same as admin access key",
+ HTTPStatusCode: http.StatusForbidden,
+ },
+ ErrAdminAccountNotEligible: {
+ Code: "XMinioInvalidIAMCredentials",
+ Description: "The administrator key is not eligible for this operation",
+ HTTPStatusCode: http.StatusForbidden,
+ },
+ ErrAccountNotEligible: {
+ Code: "XMinioInvalidIAMCredentials",
+ Description: "The account key is not eligible for this operation",
+ HTTPStatusCode: http.StatusForbidden,
+ },
+ ErrAdminServiceAccountNotFound: {
+ Code: "XMinioInvalidIAMCredentials",
+ Description: "The specified service account is not found",
+ HTTPStatusCode: http.StatusNotFound,
+ },
+ ErrPostPolicyConditionInvalidFormat: {
+ Code: "PostPolicyInvalidKeyName",
+ Description: "Invalid according to Policy: Policy Condition failed",
+ HTTPStatusCode: http.StatusForbidden,
+ },
+ // Add your error structure here.
+}
+
+// toAPIErrorCode - Converts embedded errors. Convenience
+// function written to handle all cases where we have known types of
+// errors returned by underlying layers.
+func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
+ if err == nil {
+ return ErrNone
+ }
+
+ // Only return ErrClientDisconnected if the provided context is actually canceled.
+ // This way downstream context.Canceled will still report ErrOperationTimedOut
+ select {
+ case <-ctx.Done():
+ if ctx.Err() == context.Canceled {
+ return ErrClientDisconnected
+ }
+ default:
+ }
+
+ switch err {
+ case errInvalidArgument:
+ apiErr = ErrAdminInvalidArgument
+ case errNoSuchUser:
+ apiErr = ErrAdminNoSuchUser
+ case errNoSuchServiceAccount:
+ apiErr = ErrAdminServiceAccountNotFound
+ case errNoSuchGroup:
+ apiErr = ErrAdminNoSuchGroup
+ case errGroupNotEmpty:
+ apiErr = ErrAdminGroupNotEmpty
+ case errNoSuchPolicy:
+ apiErr = ErrAdminNoSuchPolicy
+ case errSignatureMismatch:
+ apiErr = ErrSignatureDoesNotMatch
+ case errInvalidRange:
+ apiErr = ErrInvalidRange
+ case errDataTooLarge:
+ apiErr = ErrEntityTooLarge
+ case errDataTooSmall:
+ apiErr = ErrEntityTooSmall
+ case errAuthentication:
+ apiErr = ErrAccessDenied
+ case auth.ErrInvalidAccessKeyLength:
+ apiErr = ErrAdminInvalidAccessKey
+ case auth.ErrInvalidSecretKeyLength:
+ apiErr = ErrAdminInvalidSecretKey
+ case errInvalidStorageClass:
+ apiErr = ErrInvalidStorageClass
+ // SSE errors
+ case errInvalidEncryptionParameters:
+ apiErr = ErrInvalidEncryptionParameters
+ case crypto.ErrInvalidEncryptionMethod:
+ apiErr = ErrInvalidEncryptionMethod
+ case crypto.ErrInvalidCustomerAlgorithm:
+ apiErr = ErrInvalidSSECustomerAlgorithm
+ case crypto.ErrMissingCustomerKey:
+ apiErr = ErrMissingSSECustomerKey
+ case crypto.ErrMissingCustomerKeyMD5:
+ apiErr = ErrMissingSSECustomerKeyMD5
+ case crypto.ErrCustomerKeyMD5Mismatch:
+ apiErr = ErrSSECustomerKeyMD5Mismatch
+ case errObjectTampered:
+ apiErr = ErrObjectTampered
+ case errEncryptedObject:
+ apiErr = ErrSSEEncryptedObject
+ case errInvalidSSEParameters:
+ apiErr = ErrInvalidSSECustomerParameters
+ case crypto.ErrInvalidCustomerKey, crypto.ErrSecretKeyMismatch:
+ apiErr = ErrAccessDenied // no access without correct key
+ case crypto.ErrIncompatibleEncryptionMethod:
+ apiErr = ErrIncompatibleEncryptionMethod
+ case errKMSNotConfigured:
+ apiErr = ErrKMSNotConfigured
+ case context.Canceled, context.DeadlineExceeded:
+ apiErr = ErrOperationTimedOut
+ case errDiskNotFound:
+ apiErr = ErrSlowDown
+ case objectlock.ErrInvalidRetentionDate:
+ apiErr = ErrInvalidRetentionDate
+ case objectlock.ErrPastObjectLockRetainDate:
+ apiErr = ErrPastObjectLockRetainDate
+ case objectlock.ErrUnknownWORMModeDirective:
+ apiErr = ErrUnknownWORMModeDirective
+ case objectlock.ErrObjectLockInvalidHeaders:
+ apiErr = ErrObjectLockInvalidHeaders
+ case objectlock.ErrMalformedXML:
+ apiErr = ErrMalformedXML
+ }
+
+ // Compression errors
+ switch err {
+ case errInvalidDecompressedSize:
+ apiErr = ErrInvalidDecompressedSize
+ }
+
+ if apiErr != ErrNone {
+ // If there was a match in the above switch case.
+ return apiErr
+ }
+
+ // etcd specific errors, a key is always a bucket for us return
+ // ErrNoSuchBucket in such a case.
+ if err == dns.ErrNoEntriesFound {
+ return ErrNoSuchBucket
+ }
+
+ switch err.(type) {
+ case StorageFull:
+ apiErr = ErrStorageFull
+ case hash.BadDigest:
+ apiErr = ErrBadDigest
+ case AllAccessDisabled:
+ apiErr = ErrAllAccessDisabled
+ case IncompleteBody:
+ apiErr = ErrIncompleteBody
+ case ObjectExistsAsDirectory:
+ apiErr = ErrObjectExistsAsDirectory
+ case PrefixAccessDenied:
+ apiErr = ErrAccessDenied
+ case BucketNameInvalid:
+ apiErr = ErrInvalidBucketName
+ case BucketNotFound:
+ apiErr = ErrNoSuchBucket
+ case BucketAlreadyOwnedByYou:
+ apiErr = ErrBucketAlreadyOwnedByYou
+ case BucketNotEmpty:
+ apiErr = ErrBucketNotEmpty
+ case BucketAlreadyExists:
+ apiErr = ErrBucketAlreadyExists
+ case BucketExists:
+ apiErr = ErrBucketAlreadyOwnedByYou
+ case ObjectNotFound:
+ apiErr = ErrNoSuchKey
+ case MethodNotAllowed:
+ apiErr = ErrMethodNotAllowed
+ case InvalidVersionID:
+ apiErr = ErrInvalidVersionID
+ case VersionNotFound:
+ apiErr = ErrNoSuchVersion
+ case ObjectAlreadyExists:
+ apiErr = ErrMethodNotAllowed
+ case ObjectNameInvalid:
+ apiErr = ErrInvalidObjectName
+ case ObjectNamePrefixAsSlash:
+ apiErr = ErrInvalidObjectNamePrefixSlash
+ case InvalidUploadID:
+ apiErr = ErrNoSuchUpload
+ case InvalidPart:
+ apiErr = ErrInvalidPart
+ case InsufficientWriteQuorum:
+ apiErr = ErrSlowDown
+ case InsufficientReadQuorum:
+ apiErr = ErrSlowDown
+ case InvalidMarkerPrefixCombination:
+ apiErr = ErrNotImplemented
+ case InvalidUploadIDKeyCombination:
+ apiErr = ErrNotImplemented
+ case MalformedUploadID:
+ apiErr = ErrNoSuchUpload
+ case PartTooSmall:
+ apiErr = ErrEntityTooSmall
+ case SignatureDoesNotMatch:
+ apiErr = ErrSignatureDoesNotMatch
+ case hash.SHA256Mismatch:
+ apiErr = ErrContentSHA256Mismatch
+ case ObjectTooLarge:
+ apiErr = ErrEntityTooLarge
+ case ObjectTooSmall:
+ apiErr = ErrEntityTooSmall
+ case NotImplemented:
+ apiErr = ErrNotImplemented
+ case PartTooBig:
+ apiErr = ErrEntityTooLarge
+ case UnsupportedMetadata:
+ apiErr = ErrUnsupportedMetadata
+ case BucketPolicyNotFound:
+ apiErr = ErrNoSuchBucketPolicy
+ case BucketLifecycleNotFound:
+ apiErr = ErrNoSuchLifecycleConfiguration
+ case BucketSSEConfigNotFound:
+ apiErr = ErrNoSuchBucketSSEConfig
+ case BucketTaggingNotFound:
+ apiErr = ErrBucketTaggingNotFound
+ case BucketObjectLockConfigNotFound:
+ apiErr = ErrObjectLockConfigurationNotFound
+ case BucketQuotaConfigNotFound:
+ apiErr = ErrAdminNoSuchQuotaConfiguration
+ case BucketReplicationConfigNotFound:
+ apiErr = ErrReplicationConfigurationNotFoundError
+ case BucketRemoteDestinationNotFound:
+ apiErr = ErrRemoteDestinationNotFoundError
+ case BucketReplicationDestinationMissingLock:
+ apiErr = ErrReplicationDestinationMissingLock
+ case BucketRemoteTargetNotFound:
+ apiErr = ErrRemoteTargetNotFoundError
+ case BucketRemoteConnectionErr:
+ apiErr = ErrReplicationRemoteConnectionError
+ case BucketRemoteAlreadyExists:
+ apiErr = ErrBucketRemoteAlreadyExists
+ case BucketRemoteLabelInUse:
+ apiErr = ErrBucketRemoteLabelInUse
+ case BucketRemoteArnTypeInvalid:
+ apiErr = ErrBucketRemoteArnTypeInvalid
+ case BucketRemoteArnInvalid:
+ apiErr = ErrBucketRemoteArnInvalid
+ case BucketRemoteRemoveDisallowed:
+ apiErr = ErrBucketRemoteRemoveDisallowed
+ case BucketRemoteTargetNotVersioned:
+ apiErr = ErrRemoteTargetNotVersionedError
+ case BucketReplicationSourceNotVersioned:
+ apiErr = ErrReplicationSourceNotVersionedError
+ case TransitionStorageClassNotFound:
+ apiErr = ErrTransitionStorageClassNotFoundError
+ case InvalidObjectState:
+ apiErr = ErrInvalidObjectState
+
+ case BucketQuotaExceeded:
+ apiErr = ErrAdminBucketQuotaExceeded
+ case *event.ErrInvalidEventName:
+ apiErr = ErrEventNotification
+ case *event.ErrInvalidARN:
+ apiErr = ErrARNNotification
+ case *event.ErrARNNotFound:
+ apiErr = ErrARNNotification
+ case *event.ErrUnknownRegion:
+ apiErr = ErrRegionNotification
+ case *event.ErrInvalidFilterName:
+ apiErr = ErrFilterNameInvalid
+ case *event.ErrFilterNamePrefix:
+ apiErr = ErrFilterNamePrefix
+ case *event.ErrFilterNameSuffix:
+ apiErr = ErrFilterNameSuffix
+ case *event.ErrInvalidFilterValue:
+ apiErr = ErrFilterValueInvalid
+ case *event.ErrDuplicateEventName:
+ apiErr = ErrOverlappingConfigs
+ case *event.ErrDuplicateQueueConfiguration:
+ apiErr = ErrOverlappingFilterNotification
+ case *event.ErrUnsupportedConfiguration:
+ apiErr = ErrUnsupportedNotification
+ case OperationTimedOut:
+ apiErr = ErrOperationTimedOut
+ case BackendDown:
+ apiErr = ErrBackendDown
+ case ObjectNameTooLong:
+ apiErr = ErrKeyTooLongError
+ case dns.ErrInvalidBucketName:
+ apiErr = ErrInvalidBucketName
+ case dns.ErrBucketConflict:
+ apiErr = ErrBucketAlreadyExists
+ default:
+ var ie, iw int
+ // This work-around is to handle the issue golang/go#30648
+ if _, ferr := fmt.Fscanf(strings.NewReader(err.Error()),
+ "request declared a Content-Length of %d but only wrote %d bytes",
+ &ie, &iw); ferr != nil {
+ apiErr = ErrInternalError
+ // Make sure to log the errors which we cannot translate
+ // to a meaningful S3 API errors. This is added to aid in
+ // debugging unexpected/unhandled errors.
+ logger.LogIf(ctx, err)
+ } else if ie > iw {
+ apiErr = ErrIncompleteBody
+ } else {
+ apiErr = ErrInternalError
+ // Make sure to log the errors which we cannot translate
+ // to a meaningful S3 API errors. This is added to aid in
+ // debugging unexpected/unhandled errors.
+ logger.LogIf(ctx, err)
+ }
+ }
+
+ return apiErr
+}
+
+var noError = APIError{}
+
+// toAPIError - Converts embedded errors. Convenience
+// function written to handle all cases where we have known types of
+// errors returned by underlying layers.
+func toAPIError(ctx context.Context, err error) APIError {
+ if err == nil {
+ return noError
+ }
+
+ var apiErr = errorCodes.ToAPIErr(toAPIErrorCode(ctx, err))
+ e, ok := err.(dns.ErrInvalidBucketName)
+ if ok {
+ code := toAPIErrorCode(ctx, e)
+ apiErr = errorCodes.ToAPIErrWithErr(code, e)
+ }
+
+ if apiErr.Code == "NotImplemented" {
+ switch e := err.(type) {
+ case NotImplemented:
+ desc := e.Error()
+ if desc == "" {
+ desc = apiErr.Description
+ }
+ apiErr = APIError{
+ Code: apiErr.Code,
+ Description: desc,
+ HTTPStatusCode: apiErr.HTTPStatusCode,
+ }
+ return apiErr
+ }
+ }
+
+ if apiErr.Code == "InternalError" {
+ // If we see an internal error try to interpret
+ // any underlying errors if possible depending on
+ // their internal error types. This code is only
+ // useful with gateway implementations.
+ switch e := err.(type) {
+ case InvalidArgument:
+ apiErr = APIError{
+ Code: "InvalidArgument",
+ Description: e.Error(),
+ HTTPStatusCode: errorCodes[ErrInvalidRequest].HTTPStatusCode,
+ }
+ case *xml.SyntaxError:
+ apiErr = APIError{
+ Code: "MalformedXML",
+ Description: fmt.Sprintf("%s (%s)", errorCodes[ErrMalformedXML].Description,
+ e.Error()),
+ HTTPStatusCode: errorCodes[ErrMalformedXML].HTTPStatusCode,
+ }
+ case url.EscapeError:
+ apiErr = APIError{
+ Code: "XMinioInvalidObjectName",
+ Description: fmt.Sprintf("%s (%s)", errorCodes[ErrInvalidObjectName].Description,
+ e.Error()),
+ HTTPStatusCode: http.StatusBadRequest,
+ }
+ case versioning.Error:
+ apiErr = APIError{
+ Code: "IllegalVersioningConfigurationException",
+ Description: fmt.Sprintf("Versioning configuration specified in the request is invalid. (%s)", e.Error()),
+ HTTPStatusCode: http.StatusBadRequest,
+ }
+ case lifecycle.Error:
+ apiErr = APIError{
+ Code: "InvalidRequest",
+ Description: e.Error(),
+ HTTPStatusCode: http.StatusBadRequest,
+ }
+ case replication.Error:
+ apiErr = APIError{
+ Code: "MalformedXML",
+ Description: e.Error(),
+ HTTPStatusCode: http.StatusBadRequest,
+ }
+ case tags.Error:
+ apiErr = APIError{
+ Code: e.Code(),
+ Description: e.Error(),
+ HTTPStatusCode: http.StatusBadRequest,
+ }
+ case policy.Error:
+ apiErr = APIError{
+ Code: "MalformedPolicy",
+ Description: e.Error(),
+ HTTPStatusCode: http.StatusBadRequest,
+ }
+ case crypto.Error:
+ apiErr = APIError{
+ Code: "XMinIOEncryptionError",
+ Description: e.Error(),
+ HTTPStatusCode: http.StatusBadRequest,
+ }
+ case minio.ErrorResponse:
+ apiErr = APIError{
+ Code: e.Code,
+ Description: e.Message,
+ HTTPStatusCode: e.StatusCode,
+ }
+ if globalIsGateway && strings.Contains(e.Message, "KMS is not configured") {
+ apiErr = APIError{
+ Code: "NotImplemented",
+ Description: e.Message,
+ HTTPStatusCode: http.StatusNotImplemented,
+ }
+ }
+ case *googleapi.Error:
+ apiErr = APIError{
+ Code: "XGCSInternalError",
+ Description: e.Message,
+ HTTPStatusCode: e.Code,
+ }
+ // GCS may send multiple errors, just pick the first one
+ // since S3 only sends one Error XML response.
+ if len(e.Errors) >= 1 {
+ apiErr.Code = e.Errors[0].Reason
+
+ }
+ case azblob.StorageError:
+ apiErr = APIError{
+ Code: string(e.ServiceCode()),
+ Description: e.Error(),
+ HTTPStatusCode: e.Response().StatusCode,
+ }
+ // Add more Gateway SDKs here if any in future.
+ default:
+ if errors.Is(err, errMalformedEncoding) {
+ apiErr = APIError{
+ Code: "BadRequest",
+ Description: err.Error(),
+ HTTPStatusCode: http.StatusBadRequest,
+ }
+ } else if errors.Is(err, errChunkTooBig) {
+ apiErr = APIError{
+ Code: "BadRequest",
+ Description: err.Error(),
+ HTTPStatusCode: http.StatusBadRequest,
+ }
+ } else if errors.Is(err, strconv.ErrRange) {
+ apiErr = APIError{
+ Code: "BadRequest",
+ Description: err.Error(),
+ HTTPStatusCode: http.StatusBadRequest,
+ }
+ } else {
+ apiErr = APIError{
+ Code: apiErr.Code,
+ Description: fmt.Sprintf("%s: cause(%v)", apiErr.Description, err),
+ HTTPStatusCode: apiErr.HTTPStatusCode,
+ }
+ }
+ }
+ }
+
+ return apiErr
+}
+
+// getAPIError provides API Error for input API error code.
+func getAPIError(code APIErrorCode) APIError {
+ if apiErr, ok := errorCodes[code]; ok {
+ return apiErr
+ }
+ return errorCodes.ToAPIErr(ErrInternalError)
+}
+
+// getErrorResponse gets in standard error and resource value and
+// provides a encodable populated response values
+func getAPIErrorResponse(ctx context.Context, err APIError, resource, requestID, hostID string) APIErrorResponse {
+ reqInfo := logger.GetReqInfo(ctx)
+ return APIErrorResponse{
+ Code: err.Code,
+ Message: err.Description,
+ BucketName: reqInfo.BucketName,
+ Key: reqInfo.ObjectName,
+ Resource: resource,
+ Region: globalServerRegion,
+ RequestID: requestID,
+ HostID: hostID,
+ }
+}
diff --git a/api/minio/api-response.go b/api/minio/api-response.go
new file mode 100644
index 000000000..dd329a3b4
--- /dev/null
+++ b/api/minio/api-response.go
@@ -0,0 +1,842 @@
+// Copyright (c) 2015-2021 MinIO, Inc.
+//
+// This file is part of MinIO Object Storage stack
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package cmd
+
+import (
+ "context"
+ "encoding/base64"
+ "encoding/xml"
+ "fmt"
+ "net/http"
+ "net/url"
+ "path"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/minio/minio/internal/crypto"
+ "github.com/minio/minio/internal/handlers"
+ xhttp "github.com/minio/minio/internal/http"
+ "github.com/minio/minio/internal/logger"
+)
+
+const (
+ // RFC3339 a subset of the ISO8601 timestamp format. e.g 2014-04-29T18:30:38Z
+ iso8601TimeFormat = "2006-01-02T15:04:05.000Z" // Reply date format with nanosecond precision.
+ maxObjectList = 1000 // Limit number of objects in a listObjectsResponse/listObjectsVersionsResponse.
+ maxDeleteList = 10000 // Limit number of objects deleted in a delete call.
+ maxUploadsList = 10000 // Limit number of uploads in a listUploadsResponse.
+ maxPartsList = 10000 // Limit number of parts in a listPartsResponse.
+)
+
+// LocationResponse - format for location response.
+type LocationResponse struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ LocationConstraint" json:"-"`
+ Location string `xml:",chardata"`
+}
+
+// PolicyStatus captures information returned by GetBucketPolicyStatusHandler
+type PolicyStatus struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ PolicyStatus" json:"-"`
+ IsPublic string
+}
+
+// ListVersionsResponse - format for list bucket versions response.
+type ListVersionsResponse struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListVersionsResult" json:"-"`
+
+ Name string
+ Prefix string
+ KeyMarker string
+
+ // When response is truncated (the IsTruncated element value in the response
+ // is true), you can use the key name in this field as marker in the subsequent
+ // request to get next set of objects. Server lists objects in alphabetical
+ // order Note: This element is returned only if you have delimiter request parameter
+ // specified. If response does not include the NextMaker and it is truncated,
+ // you can use the value of the last Key in the response as the marker in the
+ // subsequent request to get the next set of object keys.
+ NextKeyMarker string `xml:"NextKeyMarker,omitempty"`
+
+ // When the number of responses exceeds the value of MaxKeys,
+ // NextVersionIdMarker specifies the first object version not
+ // returned that satisfies the search criteria. Use this value
+ // for the version-id-marker request parameter in a subsequent request.
+ NextVersionIDMarker string `xml:"NextVersionIdMarker"`
+
+ // Marks the last version of the Key returned in a truncated response.
+ VersionIDMarker string `xml:"VersionIdMarker"`
+
+ MaxKeys int
+ Delimiter string
+ // A flag that indicates whether or not ListObjects returned all of the results
+ // that satisfied the search criteria.
+ IsTruncated bool
+
+ CommonPrefixes []CommonPrefix
+ Versions []ObjectVersion
+
+ // Encoding type used to encode object keys in the response.
+ EncodingType string `xml:"EncodingType,omitempty"`
+}
+
+// ListObjectsResponse - format for list objects response.
+type ListObjectsResponse struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListBucketResult" json:"-"`
+
+ Name string
+ Prefix string
+ Marker string
+
+ // When response is truncated (the IsTruncated element value in the response
+ // is true), you can use the key name in this field as marker in the subsequent
+ // request to get next set of objects. Server lists objects in alphabetical
+ // order Note: This element is returned only if you have delimiter request parameter
+ // specified. If response does not include the NextMaker and it is truncated,
+ // you can use the value of the last Key in the response as the marker in the
+ // subsequent request to get the next set of object keys.
+ NextMarker string `xml:"NextMarker,omitempty"`
+
+ MaxKeys int
+ Delimiter string
+ // A flag that indicates whether or not ListObjects returned all of the results
+ // that satisfied the search criteria.
+ IsTruncated bool
+
+ Contents []Object
+ CommonPrefixes []CommonPrefix
+
+ // Encoding type used to encode object keys in the response.
+ EncodingType string `xml:"EncodingType,omitempty"`
+}
+
+// ListObjectsV2Response - format for list objects response.
+type ListObjectsV2Response struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListBucketResult" json:"-"`
+
+ Name string
+ Prefix string
+ StartAfter string `xml:"StartAfter,omitempty"`
+ // When response is truncated (the IsTruncated element value in the response
+ // is true), you can use the key name in this field as marker in the subsequent
+ // request to get next set of objects. Server lists objects in alphabetical
+ // order Note: This element is returned only if you have delimiter request parameter
+ // specified. If response does not include the NextMaker and it is truncated,
+ // you can use the value of the last Key in the response as the marker in the
+ // subsequent request to get the next set of object keys.
+ ContinuationToken string `xml:"ContinuationToken,omitempty"`
+ NextContinuationToken string `xml:"NextContinuationToken,omitempty"`
+
+ KeyCount int
+ MaxKeys int
+ Delimiter string
+ // A flag that indicates whether or not ListObjects returned all of the results
+ // that satisfied the search criteria.
+ IsTruncated bool
+
+ Contents []Object
+ CommonPrefixes []CommonPrefix
+
+ // Encoding type used to encode object keys in the response.
+ EncodingType string `xml:"EncodingType,omitempty"`
+}
+
+// Part container for part metadata.
+type Part struct {
+ PartNumber int
+ LastModified string
+ ETag string
+ Size int64
+}
+
+// ListPartsResponse - format for list parts response.
+type ListPartsResponse struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListPartsResult" json:"-"`
+
+ Bucket string
+ Key string
+ UploadID string `xml:"UploadId"`
+
+ Initiator Initiator
+ Owner Owner
+
+ // The class of storage used to store the object.
+ StorageClass string
+
+ PartNumberMarker int
+ NextPartNumberMarker int
+ MaxParts int
+ IsTruncated bool
+
+ // List of parts.
+ Parts []Part `xml:"Part"`
+}
+
+// ListMultipartUploadsResponse - format for list multipart uploads response.
+type ListMultipartUploadsResponse struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListMultipartUploadsResult" json:"-"`
+
+ Bucket string
+ KeyMarker string
+ UploadIDMarker string `xml:"UploadIdMarker"`
+ NextKeyMarker string
+ NextUploadIDMarker string `xml:"NextUploadIdMarker"`
+ Delimiter string
+ Prefix string
+ EncodingType string `xml:"EncodingType,omitempty"`
+ MaxUploads int
+ IsTruncated bool
+
+ // List of pending uploads.
+ Uploads []Upload `xml:"Upload"`
+
+ // Delimed common prefixes.
+ CommonPrefixes []CommonPrefix
+}
+
+// ListBucketsResponse - format for list buckets response
+type ListBucketsResponse struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListAllMyBucketsResult" json:"-"`
+
+ Owner Owner
+
+ // Container for one or more buckets.
+ Buckets struct {
+ Buckets []Bucket `xml:"Bucket"`
+ } // Buckets are nested
+}
+
+// Upload container for in progress multipart upload
+type Upload struct {
+ Key string
+ UploadID string `xml:"UploadId"`
+ Initiator Initiator
+ Owner Owner
+ StorageClass string
+ Initiated string
+}
+
+// CommonPrefix container for prefix response in ListObjectsResponse
+type CommonPrefix struct {
+ Prefix string
+}
+
+// Bucket container for bucket metadata
+type Bucket struct {
+ Name string
+ CreationDate string // time string of format "2006-01-02T15:04:05.000Z"
+}
+
+// ObjectVersion container for object version metadata
+type ObjectVersion struct {
+ Object
+ IsLatest bool
+ VersionID string `xml:"VersionId"`
+
+ isDeleteMarker bool
+}
+
+// MarshalXML - marshal ObjectVersion
+func (o ObjectVersion) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if o.isDeleteMarker {
+ start.Name.Local = "DeleteMarker"
+ } else {
+ start.Name.Local = "Version"
+ }
+
+ type objectVersionWrapper ObjectVersion
+ return e.EncodeElement(objectVersionWrapper(o), start)
+}
+
+// StringMap is a map[string]string.
+type StringMap map[string]string
+
+// MarshalXML - StringMap marshals into XML.
+func (s StringMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+
+ tokens := []xml.Token{start}
+
+ for key, value := range s {
+ t := xml.StartElement{}
+ t.Name = xml.Name{
+ Space: "",
+ Local: key,
+ }
+ tokens = append(tokens, t, xml.CharData(value), xml.EndElement{Name: t.Name})
+ }
+
+ tokens = append(tokens, xml.EndElement{
+ Name: start.Name,
+ })
+
+ for _, t := range tokens {
+ if err := e.EncodeToken(t); err != nil {
+ return err
+ }
+ }
+
+ // flush to ensure tokens are written
+ return e.Flush()
+}
+
+// Object container for object metadata
+type Object struct {
+ Key string
+ LastModified string // time string of format "2006-01-02T15:04:05.000Z"
+ ETag string
+ Size int64
+
+ // Owner of the object.
+ Owner Owner
+
+ // The class of storage used to store the object.
+ StorageClass string
+
+ // UserMetadata user-defined metadata
+ UserMetadata StringMap `xml:"UserMetadata,omitempty"`
+}
+
+// CopyObjectResponse container returns ETag and LastModified of the successfully copied object
+type CopyObjectResponse struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CopyObjectResult" json:"-"`
+ LastModified string // time string of format "2006-01-02T15:04:05.000Z"
+ ETag string // md5sum of the copied object.
+}
+
+// CopyObjectPartResponse container returns ETag and LastModified of the successfully copied object
+type CopyObjectPartResponse struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CopyPartResult" json:"-"`
+ LastModified string // time string of format "2006-01-02T15:04:05.000Z"
+ ETag string // md5sum of the copied object part.
+}
+
+// Initiator inherit from Owner struct, fields are same
+type Initiator Owner
+
+// Owner - bucket owner/principal
+type Owner struct {
+ ID string
+ DisplayName string
+}
+
+// InitiateMultipartUploadResponse container for InitiateMultiPartUpload response, provides uploadID to start MultiPart upload
+type InitiateMultipartUploadResponse struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ InitiateMultipartUploadResult" json:"-"`
+
+ Bucket string
+ Key string
+ UploadID string `xml:"UploadId"`
+}
+
+// CompleteMultipartUploadResponse container for completed multipart upload response
+type CompleteMultipartUploadResponse struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CompleteMultipartUploadResult" json:"-"`
+
+ Location string
+ Bucket string
+ Key string
+ ETag string
+}
+
+// DeleteError structure.
+type DeleteError struct {
+ Code string
+ Message string
+ Key string
+ VersionID string `xml:"VersionId"`
+}
+
+// DeleteObjectsResponse container for multiple object deletes.
+type DeleteObjectsResponse struct {
+ XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ DeleteResult" json:"-"`
+
+ // Collection of all deleted objects
+ DeletedObjects []DeletedObject `xml:"Deleted,omitempty"`
+
+ // Collection of errors deleting certain objects.
+ Errors []DeleteError `xml:"Error,omitempty"`
+}
+
+// PostResponse container for POST object request when success_action_status is set to 201
+type PostResponse struct {
+ Bucket string
+ Key string
+ ETag string
+ Location string
+}
+
+// returns "https" if the tls boolean is true, "http" otherwise.
+func getURLScheme(tls bool) string {
+ if tls {
+ return httpsScheme
+ }
+ return httpScheme
+}
+
+// getObjectLocation gets the fully qualified URL of an object.
+func getObjectLocation(r *http.Request, domains []string, bucket, object string) string {
+ // unit tests do not have host set.
+ if r.Host == "" {
+ return path.Clean(r.URL.Path)
+ }
+ proto := handlers.GetSourceScheme(r)
+ if proto == "" {
+ proto = getURLScheme(globalIsTLS)
+ }
+ u := &url.URL{
+ Host: r.Host,
+ Path: path.Join(SlashSeparator, bucket, object),
+ Scheme: proto,
+ }
+ // If domain is set then we need to use bucket DNS style.
+ for _, domain := range domains {
+ if strings.HasPrefix(r.Host, bucket+"."+domain) {
+ u.Path = path.Join(SlashSeparator, object)
+ break
+ }
+ }
+ return u.String()
+}
+
+// generates ListBucketsResponse from array of BucketInfo which can be
+// serialized to match XML and JSON API spec output.
+func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
+ listbuckets := make([]Bucket, 0, len(buckets))
+ var data = ListBucketsResponse{}
+ var owner = Owner{
+ ID: globalMinioDefaultOwnerID,
+ DisplayName: "minio",
+ }
+
+ for _, bucket := range buckets {
+ var listbucket = Bucket{}
+ listbucket.Name = bucket.Name
+ listbucket.CreationDate = bucket.Created.UTC().Format(iso8601TimeFormat)
+ listbuckets = append(listbuckets, listbucket)
+ }
+
+ data.Owner = owner
+ data.Buckets.Buckets = listbuckets
+
+ return data
+}
+
+// generates an ListBucketVersions response for the said bucket with other enumerated options.
+func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo) ListVersionsResponse {
+ versions := make([]ObjectVersion, 0, len(resp.Objects))
+ var owner = Owner{
+ ID: globalMinioDefaultOwnerID,
+ DisplayName: "minio",
+ }
+ var data = ListVersionsResponse{}
+
+ for _, object := range resp.Objects {
+ var content = ObjectVersion{}
+ if object.Name == "" {
+ continue
+ }
+ content.Key = s3EncodeName(object.Name, encodingType)
+ content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
+ if object.ETag != "" {
+ content.ETag = "\"" + object.ETag + "\""
+ }
+ content.Size = object.Size
+ if object.StorageClass != "" {
+ content.StorageClass = object.StorageClass
+ } else {
+ content.StorageClass = globalMinioDefaultStorageClass
+ }
+ content.Owner = owner
+ content.VersionID = object.VersionID
+ if content.VersionID == "" {
+ content.VersionID = nullVersionID
+ }
+ content.IsLatest = object.IsLatest
+ content.isDeleteMarker = object.DeleteMarker
+ versions = append(versions, content)
+ }
+
+ data.Name = bucket
+ data.Versions = versions
+ data.EncodingType = encodingType
+ data.Prefix = s3EncodeName(prefix, encodingType)
+ data.KeyMarker = s3EncodeName(marker, encodingType)
+ data.Delimiter = s3EncodeName(delimiter, encodingType)
+ data.MaxKeys = maxKeys
+
+ data.NextKeyMarker = s3EncodeName(resp.NextMarker, encodingType)
+ data.NextVersionIDMarker = resp.NextVersionIDMarker
+ data.VersionIDMarker = versionIDMarker
+ data.IsTruncated = resp.IsTruncated
+
+ prefixes := make([]CommonPrefix, 0, len(resp.Prefixes))
+ for _, prefix := range resp.Prefixes {
+ var prefixItem = CommonPrefix{}
+ prefixItem.Prefix = s3EncodeName(prefix, encodingType)
+ prefixes = append(prefixes, prefixItem)
+ }
+ data.CommonPrefixes = prefixes
+ return data
+}
+
+// generates an ListObjectsV1 response for the said bucket with other enumerated options.
+func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
+ contents := make([]Object, 0, len(resp.Objects))
+ var owner = Owner{
+ ID: globalMinioDefaultOwnerID,
+ DisplayName: "minio",
+ }
+ var data = ListObjectsResponse{}
+
+ for _, object := range resp.Objects {
+ var content = Object{}
+ if object.Name == "" {
+ continue
+ }
+ content.Key = s3EncodeName(object.Name, encodingType)
+ content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
+ if object.ETag != "" {
+ content.ETag = "\"" + object.ETag + "\""
+ }
+ content.Size = object.Size
+ if object.StorageClass != "" {
+ content.StorageClass = object.StorageClass
+ } else {
+ content.StorageClass = globalMinioDefaultStorageClass
+ }
+ content.Owner = owner
+ contents = append(contents, content)
+ }
+ data.Name = bucket
+ data.Contents = contents
+
+ data.EncodingType = encodingType
+ data.Prefix = s3EncodeName(prefix, encodingType)
+ data.Marker = s3EncodeName(marker, encodingType)
+ data.Delimiter = s3EncodeName(delimiter, encodingType)
+ data.MaxKeys = maxKeys
+ data.NextMarker = s3EncodeName(resp.NextMarker, encodingType)
+ data.IsTruncated = resp.IsTruncated
+
+ prefixes := make([]CommonPrefix, 0, len(resp.Prefixes))
+ for _, prefix := range resp.Prefixes {
+ var prefixItem = CommonPrefix{}
+ prefixItem.Prefix = s3EncodeName(prefix, encodingType)
+ prefixes = append(prefixes, prefixItem)
+ }
+ data.CommonPrefixes = prefixes
+ return data
+}
+
+// generates an ListObjectsV2 response for the said bucket with other enumerated options.
+func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter, delimiter, encodingType string, fetchOwner, isTruncated bool, maxKeys int, objects []ObjectInfo, prefixes []string, metadata bool) ListObjectsV2Response {
+ contents := make([]Object, 0, len(objects))
+ var owner = Owner{
+ ID: globalMinioDefaultOwnerID,
+ DisplayName: "minio",
+ }
+ var data = ListObjectsV2Response{}
+
+ for _, object := range objects {
+ var content = Object{}
+ if object.Name == "" {
+ continue
+ }
+ content.Key = s3EncodeName(object.Name, encodingType)
+ content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
+ if object.ETag != "" {
+ content.ETag = "\"" + object.ETag + "\""
+ }
+ content.Size = object.Size
+ if object.StorageClass != "" {
+ content.StorageClass = object.StorageClass
+ } else {
+ content.StorageClass = globalMinioDefaultStorageClass
+ }
+ content.Owner = owner
+ if metadata {
+ content.UserMetadata = make(StringMap)
+ switch kind, _ := crypto.IsEncrypted(object.UserDefined); kind {
+ case crypto.S3:
+ content.UserMetadata[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionAES
+ case crypto.S3KMS:
+ content.UserMetadata[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionKMS
+ case crypto.SSEC:
+ content.UserMetadata[xhttp.AmzServerSideEncryptionCustomerAlgorithm] = xhttp.AmzEncryptionAES
+ }
+ for k, v := range CleanMinioInternalMetadataKeys(object.UserDefined) {
+ if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
+ // Do not need to send any internal metadata
+ // values to client.
+ continue
+ }
+ // https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
+ if equals(k, xhttp.AmzMetaUnencryptedContentLength, xhttp.AmzMetaUnencryptedContentMD5) {
+ continue
+ }
+ content.UserMetadata[k] = v
+ }
+ }
+ contents = append(contents, content)
+ }
+ data.Name = bucket
+ data.Contents = contents
+
+ data.EncodingType = encodingType
+ data.StartAfter = s3EncodeName(startAfter, encodingType)
+ data.Delimiter = s3EncodeName(delimiter, encodingType)
+ data.Prefix = s3EncodeName(prefix, encodingType)
+ data.MaxKeys = maxKeys
+ data.ContinuationToken = base64.StdEncoding.EncodeToString([]byte(token))
+ data.NextContinuationToken = base64.StdEncoding.EncodeToString([]byte(nextToken))
+ data.IsTruncated = isTruncated
+
+ commonPrefixes := make([]CommonPrefix, 0, len(prefixes))
+ for _, prefix := range prefixes {
+ var prefixItem = CommonPrefix{}
+ prefixItem.Prefix = s3EncodeName(prefix, encodingType)
+ commonPrefixes = append(commonPrefixes, prefixItem)
+ }
+ data.CommonPrefixes = commonPrefixes
+ data.KeyCount = len(data.Contents) + len(data.CommonPrefixes)
+ return data
+}
+
+// generates CopyObjectResponse from etag and lastModified time.
+func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectResponse {
+ return CopyObjectResponse{
+ ETag: "\"" + etag + "\"",
+ LastModified: lastModified.UTC().Format(iso8601TimeFormat),
+ }
+}
+
+// generates CopyObjectPartResponse from etag and lastModified time.
+func generateCopyObjectPartResponse(etag string, lastModified time.Time) CopyObjectPartResponse {
+ return CopyObjectPartResponse{
+ ETag: "\"" + etag + "\"",
+ LastModified: lastModified.UTC().Format(iso8601TimeFormat),
+ }
+}
+
+// generates InitiateMultipartUploadResponse for given bucket, key and uploadID.
+func generateInitiateMultipartUploadResponse(bucket, key, uploadID string) InitiateMultipartUploadResponse {
+ return InitiateMultipartUploadResponse{
+ Bucket: bucket,
+ Key: key,
+ UploadID: uploadID,
+ }
+}
+
+// generates CompleteMultipartUploadResponse for given bucket, key, location and ETag.
+func generateCompleteMultpartUploadResponse(bucket, key, location, etag string) CompleteMultipartUploadResponse {
+ return CompleteMultipartUploadResponse{
+ Location: location,
+ Bucket: bucket,
+ Key: key,
+ // AWS S3 quotes the ETag in XML, make sure we are compatible here.
+ ETag: "\"" + etag + "\"",
+ }
+}
+
+// generates ListPartsResponse from ListPartsInfo.
+func generateListPartsResponse(partsInfo ListPartsInfo, encodingType string) ListPartsResponse {
+ listPartsResponse := ListPartsResponse{}
+ listPartsResponse.Bucket = partsInfo.Bucket
+ listPartsResponse.Key = s3EncodeName(partsInfo.Object, encodingType)
+ listPartsResponse.UploadID = partsInfo.UploadID
+ listPartsResponse.StorageClass = globalMinioDefaultStorageClass
+
+ // Dumb values not meaningful
+ listPartsResponse.Initiator = Initiator{
+ ID: globalMinioDefaultOwnerID,
+ DisplayName: globalMinioDefaultOwnerID,
+ }
+ listPartsResponse.Owner = Owner{
+ ID: globalMinioDefaultOwnerID,
+ DisplayName: globalMinioDefaultOwnerID,
+ }
+
+ listPartsResponse.MaxParts = partsInfo.MaxParts
+ listPartsResponse.PartNumberMarker = partsInfo.PartNumberMarker
+ listPartsResponse.IsTruncated = partsInfo.IsTruncated
+ listPartsResponse.NextPartNumberMarker = partsInfo.NextPartNumberMarker
+
+ listPartsResponse.Parts = make([]Part, len(partsInfo.Parts))
+ for index, part := range partsInfo.Parts {
+ newPart := Part{}
+ newPart.PartNumber = part.PartNumber
+ newPart.ETag = "\"" + part.ETag + "\""
+ newPart.Size = part.Size
+ newPart.LastModified = part.LastModified.UTC().Format(iso8601TimeFormat)
+ listPartsResponse.Parts[index] = newPart
+ }
+ return listPartsResponse
+}
+
+// generates ListMultipartUploadsResponse for given bucket and ListMultipartsInfo.
+func generateListMultipartUploadsResponse(bucket string, multipartsInfo ListMultipartsInfo, encodingType string) ListMultipartUploadsResponse {
+ listMultipartUploadsResponse := ListMultipartUploadsResponse{}
+ listMultipartUploadsResponse.Bucket = bucket
+ listMultipartUploadsResponse.Delimiter = s3EncodeName(multipartsInfo.Delimiter, encodingType)
+ listMultipartUploadsResponse.IsTruncated = multipartsInfo.IsTruncated
+ listMultipartUploadsResponse.EncodingType = encodingType
+ listMultipartUploadsResponse.Prefix = s3EncodeName(multipartsInfo.Prefix, encodingType)
+ listMultipartUploadsResponse.KeyMarker = s3EncodeName(multipartsInfo.KeyMarker, encodingType)
+ listMultipartUploadsResponse.NextKeyMarker = s3EncodeName(multipartsInfo.NextKeyMarker, encodingType)
+ listMultipartUploadsResponse.MaxUploads = multipartsInfo.MaxUploads
+ listMultipartUploadsResponse.NextUploadIDMarker = multipartsInfo.NextUploadIDMarker
+ listMultipartUploadsResponse.UploadIDMarker = multipartsInfo.UploadIDMarker
+ listMultipartUploadsResponse.CommonPrefixes = make([]CommonPrefix, len(multipartsInfo.CommonPrefixes))
+ for index, commonPrefix := range multipartsInfo.CommonPrefixes {
+ listMultipartUploadsResponse.CommonPrefixes[index] = CommonPrefix{
+ Prefix: s3EncodeName(commonPrefix, encodingType),
+ }
+ }
+ listMultipartUploadsResponse.Uploads = make([]Upload, len(multipartsInfo.Uploads))
+ for index, upload := range multipartsInfo.Uploads {
+ newUpload := Upload{}
+ newUpload.UploadID = upload.UploadID
+ newUpload.Key = s3EncodeName(upload.Object, encodingType)
+ newUpload.Initiated = upload.Initiated.UTC().Format(iso8601TimeFormat)
+ listMultipartUploadsResponse.Uploads[index] = newUpload
+ }
+ return listMultipartUploadsResponse
+}
+
+// generate multi objects delete response.
+func generateMultiDeleteResponse(quiet bool, deletedObjects []DeletedObject, errs []DeleteError) DeleteObjectsResponse {
+ deleteResp := DeleteObjectsResponse{}
+ if !quiet {
+ deleteResp.DeletedObjects = deletedObjects
+ }
+ if len(errs) == len(deletedObjects) {
+ deleteResp.DeletedObjects = nil
+ }
+ deleteResp.Errors = errs
+ return deleteResp
+}
+
+func writeResponse(w http.ResponseWriter, statusCode int, response []byte, mType mimeType) {
+ setCommonHeaders(w)
+ if mType != mimeNone {
+ w.Header().Set(xhttp.ContentType, string(mType))
+ }
+ w.Header().Set(xhttp.ContentLength, strconv.Itoa(len(response)))
+ w.WriteHeader(statusCode)
+ if response != nil {
+ w.Write(response)
+ w.(http.Flusher).Flush()
+ }
+}
+
+// mimeType represents various MIME type used API responses.
+type mimeType string
+
+const (
+ // Means no response type.
+ mimeNone mimeType = ""
+ // Means response type is JSON.
+ mimeJSON mimeType = "application/json"
+ // Means response type is XML.
+ mimeXML mimeType = "application/xml"
+)
+
+// writeSuccessResponseJSON writes success headers and response if any,
+// with content-type set to `application/json`.
+func writeSuccessResponseJSON(w http.ResponseWriter, response []byte) {
+ writeResponse(w, http.StatusOK, response, mimeJSON)
+}
+
+// writeSuccessResponseXML writes success headers and response if any,
+// with content-type set to `application/xml`.
+func writeSuccessResponseXML(w http.ResponseWriter, response []byte) {
+ writeResponse(w, http.StatusOK, response, mimeXML)
+}
+
+// writeSuccessNoContent writes success headers with http status 204
+func writeSuccessNoContent(w http.ResponseWriter) {
+ writeResponse(w, http.StatusNoContent, nil, mimeNone)
+}
+
+// writeRedirectSeeOther writes Location header with http status 303
+func writeRedirectSeeOther(w http.ResponseWriter, location string) {
+ w.Header().Set(xhttp.Location, location)
+ writeResponse(w, http.StatusSeeOther, nil, mimeNone)
+}
+
+func writeSuccessResponseHeadersOnly(w http.ResponseWriter) {
+ writeResponse(w, http.StatusOK, nil, mimeNone)
+}
+
+// writeErrorRespone writes error headers
+func writeErrorResponse(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(xhttp.RetryAfter, "120")
+ case "InvalidRegion":
+ err.Description = fmt.Sprintf("Region does not match; expecting '%s'.", globalServerRegion)
+ case "AuthorizationHeaderMalformed":
+ err.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalServerRegion)
+ }
+
+ // Generate error response.
+ errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path,
+ w.Header().Get(xhttp.AmzRequestID), globalDeploymentID)
+ encodedErrorResponse := encodeResponse(errorResponse)
+ writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML)
+}
+
+func writeErrorResponseHeadersOnly(w http.ResponseWriter, err APIError) {
+ writeResponse(w, err.HTTPStatusCode, nil, mimeNone)
+}
+
+func writeErrorResponseString(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
+ // Generate string error response.
+ writeResponse(w, err.HTTPStatusCode, []byte(err.Description), mimeNone)
+}
+
+// writeErrorResponseJSON - writes error response in JSON format;
+// useful for admin APIs.
+func writeErrorResponseJSON(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
+ // Generate error response.
+ errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path, w.Header().Get(xhttp.AmzRequestID), globalDeploymentID)
+ encodedErrorResponse := encodeResponseJSON(errorResponse)
+ writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
+}
+
+// writeCustomErrorResponseJSON - similar to writeErrorResponseJSON,
+// but accepts the error message directly (this allows messages to be
+// dynamically generated.)
+func writeCustomErrorResponseJSON(ctx context.Context, w http.ResponseWriter, err APIError,
+ errBody string, reqURL *url.URL) {
+
+ reqInfo := logger.GetReqInfo(ctx)
+ errorResponse := APIErrorResponse{
+ Code: err.Code,
+ Message: errBody,
+ Resource: reqURL.Path,
+ BucketName: reqInfo.BucketName,
+ Key: reqInfo.ObjectName,
+ RequestID: w.Header().Get(xhttp.AmzRequestID),
+ HostID: globalDeploymentID,
+ }
+ encodedErrorResponse := encodeResponseJSON(errorResponse)
+ writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
+}
diff --git a/api/minio/api-utils.go b/api/minio/api-utils.go
new file mode 100644
index 000000000..fa277b9fc
--- /dev/null
+++ b/api/minio/api-utils.go
@@ -0,0 +1,108 @@
+// Copyright (c) 2015-2021 MinIO, Inc.
+//
+// This file is part of MinIO Object Storage stack
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package cmd
+
+import (
+ "strings"
+)
+
+func shouldEscape(c byte) bool {
+ if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
+ return false
+ }
+
+ switch c {
+ case '-', '_', '.', '/', '*':
+ return false
+ }
+ return true
+}
+
+// s3URLEncode is based on Golang's url.QueryEscape() code,
+// while considering some S3 exceptions:
+// - Avoid encoding '/' and '*'
+// - Force encoding of '~'
+func s3URLEncode(s string) string {
+ spaceCount, hexCount := 0, 0
+ for i := 0; i < len(s); i++ {
+ c := s[i]
+ if shouldEscape(c) {
+ if c == ' ' {
+ spaceCount++
+ } else {
+ hexCount++
+ }
+ }
+ }
+
+ if spaceCount == 0 && hexCount == 0 {
+ return s
+ }
+
+ var buf [64]byte
+ var t []byte
+
+ required := len(s) + 2*hexCount
+ if required <= len(buf) {
+ t = buf[:required]
+ } else {
+ t = make([]byte, required)
+ }
+
+ if hexCount == 0 {
+ copy(t, s)
+ for i := 0; i < len(s); i++ {
+ if s[i] == ' ' {
+ t[i] = '+'
+ }
+ }
+ return string(t)
+ }
+
+ j := 0
+ for i := 0; i < len(s); i++ {
+ switch c := s[i]; {
+ case c == ' ':
+ t[j] = '+'
+ j++
+ case shouldEscape(c):
+ t[j] = '%'
+ t[j+1] = "0123456789ABCDEF"[c>>4]
+ t[j+2] = "0123456789ABCDEF"[c&15]
+ j += 3
+ default:
+ t[j] = s[i]
+ j++
+ }
+ }
+ return string(t)
+}
+
+// s3EncodeName encodes string in response when encodingType is specified in AWS S3 requests.
+func s3EncodeName(name string, encodingType string) (result string) {
+ // Quick path to exit
+ if encodingType == "" {
+ return name
+ }
+ encodingType = strings.ToLower(encodingType)
+ switch encodingType {
+ case "url":
+ return s3URLEncode(name)
+ }
+ return name
+}
diff --git a/api/minio/header.go b/api/minio/header.go
new file mode 100644
index 000000000..58903c46a
--- /dev/null
+++ b/api/minio/header.go
@@ -0,0 +1,86 @@
+// Copyright (c) 2015-2021 MinIO, Inc.
+//
+// This file is part of MinIO Object Storage stack
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package crypto
+
+import (
+ "bytes"
+ "crypto/md5"
+ "encoding/base64"
+ "net/http"
+
+ xhttp "github.com/minio/minio/internal/http"
+)
+
+// RemoveSensitiveHeaders removes confidential encryption
+// information - e.g. the SSE-C key - from the HTTP headers.
+// It has the same semantics as RemoveSensitiveEntires.
+func RemoveSensitiveHeaders(h http.Header) {
+ h.Del(xhttp.AmzServerSideEncryptionCustomerKey)
+ h.Del(xhttp.AmzServerSideEncryptionCopyCustomerKey)
+ h.Del(xhttp.AmzMetaUnencryptedContentLength)
+ h.Del(xhttp.AmzMetaUnencryptedContentMD5)
+}
+
+var (
+ // SSECopy represents AWS SSE-C for copy requests. It provides
+ // functionality to handle SSE-C copy requests.
+ SSECopy = ssecCopy{}
+)
+
+type ssecCopy struct{}
+
+// IsRequested returns true if the HTTP headers contains
+// at least one SSE-C copy header. Regular SSE-C headers
+// are ignored.
+func (ssecCopy) IsRequested(h http.Header) bool {
+ if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm]; ok {
+ return true
+ }
+ if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerKey]; ok {
+ return true
+ }
+ if _, ok := h[xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5]; ok {
+ return true
+ }
+ return false
+}
+
+// ParseHTTP parses the SSE-C copy headers and returns the SSE-C client key
+// on success. Regular SSE-C headers are ignored.
+func (ssecCopy) ParseHTTP(h http.Header) (key [32]byte, err error) {
+ if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm) != xhttp.AmzEncryptionAES {
+ return key, ErrInvalidCustomerAlgorithm
+ }
+ if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKey) == "" {
+ return key, ErrMissingCustomerKey
+ }
+ if h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5) == "" {
+ return key, ErrMissingCustomerKeyMD5
+ }
+
+ clientKey, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKey))
+ if err != nil || len(clientKey) != 32 { // The client key must be 256 bits long
+ return key, ErrInvalidCustomerKey
+ }
+ keyMD5, err := base64.StdEncoding.DecodeString(h.Get(xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5))
+ if md5Sum := md5.Sum(clientKey); err != nil || !bytes.Equal(md5Sum[:], keyMD5) {
+ return key, ErrCustomerKeyMD5Mismatch
+ }
+ copy(key[:], clientKey)
+ return key, nil
+}
diff --git a/api/minio/proxy.go b/api/minio/proxy.go
new file mode 100644
index 000000000..f23d4fd73
--- /dev/null
+++ b/api/minio/proxy.go
@@ -0,0 +1,127 @@
+// Copyright (c) 2015-2021 MinIO, Inc.
+//
+// This file is part of MinIO Object Storage stack
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+// Originally from https://github.com/gorilla/handlers with following license
+// https://raw.githubusercontent.com/gorilla/handlers/master/LICENSE, forked
+// and heavily modified for MinIO's internal needs.
+
+package handlers
+
+import (
+ "net"
+ "net/http"
+ "regexp"
+ "strings"
+)
+
+var (
+ // De-facto standard header keys.
+ xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For")
+ xForwardedHost = http.CanonicalHeaderKey("X-Forwarded-Host")
+ xForwardedPort = http.CanonicalHeaderKey("X-Forwarded-Port")
+ xForwardedProto = http.CanonicalHeaderKey("X-Forwarded-Proto")
+ xForwardedScheme = http.CanonicalHeaderKey("X-Forwarded-Scheme")
+ xRealIP = http.CanonicalHeaderKey("X-Real-IP")
+)
+
+var (
+ // RFC7239 defines a new "Forwarded: " header designed to replace the
+ // existing use of X-Forwarded-* headers.
+ // e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43
+ forwarded = http.CanonicalHeaderKey("Forwarded")
+ // Allows for a sub-match of the first value after 'for=' to the next
+ // comma, semi-colon or space. The match is case-insensitive.
+ forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)(.*)`)
+ // Allows for a sub-match for the first instance of scheme (http|https)
+ // prefixed by 'proto='. The match is case-insensitive.
+ protoRegex = regexp.MustCompile(`(?i)^(;|,| )+(?:proto=)(https|http)`)
+)
+
+// GetSourceScheme retrieves the scheme from the X-Forwarded-Proto and RFC7239
+// Forwarded headers (in that order).
+func GetSourceScheme(r *http.Request) string {
+ var scheme string
+
+ // Retrieve the scheme from X-Forwarded-Proto.
+ if proto := r.Header.Get(xForwardedProto); proto != "" {
+ scheme = strings.ToLower(proto)
+ } else if proto = r.Header.Get(xForwardedScheme); proto != "" {
+ scheme = strings.ToLower(proto)
+ } else if proto := r.Header.Get(forwarded); proto != "" {
+ // match should contain at least two elements if the protocol was
+ // specified in the Forwarded header. The first element will always be
+ // the 'for=', which we ignore, subsequently we proceed to look for
+ // 'proto=' which should precede right after `for=` if not
+ // we simply ignore the values and return empty. This is in line
+ // with the approach we took for returning first ip from multiple
+ // params.
+ if match := forRegex.FindStringSubmatch(proto); len(match) > 1 {
+ if match = protoRegex.FindStringSubmatch(match[2]); len(match) > 1 {
+ scheme = strings.ToLower(match[2])
+ }
+ }
+ }
+
+ return scheme
+}
+
+// GetSourceIPFromHeaders retrieves the IP from the X-Forwarded-For, X-Real-IP
+// and RFC7239 Forwarded headers (in that order)
+func GetSourceIPFromHeaders(r *http.Request) string {
+ var addr string
+
+ if fwd := r.Header.Get(xForwardedFor); fwd != "" {
+ // Only grab the first (client) address. Note that '192.168.0.1,
+ // 10.1.1.1' is a valid key for X-Forwarded-For where addresses after
+ // the first may represent forwarding proxies earlier in the chain.
+ s := strings.Index(fwd, ", ")
+ if s == -1 {
+ s = len(fwd)
+ }
+ addr = fwd[:s]
+ } else if fwd := r.Header.Get(xRealIP); fwd != "" {
+ // X-Real-IP should only contain one IP address (the client making the
+ // request).
+ addr = fwd
+ } else if fwd := r.Header.Get(forwarded); fwd != "" {
+ // match should contain at least two elements if the protocol was
+ // specified in the Forwarded header. The first element will always be
+ // the 'for=' capture, which we ignore. In the case of multiple IP
+ // addresses (for=8.8.8.8, 8.8.4.4, 172.16.1.20 is valid) we only
+ // extract the first, which should be the client IP.
+ if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 {
+ // IPv6 addresses in Forwarded headers are quoted-strings. We strip
+ // these quotes.
+ addr = strings.Trim(match[1], `"`)
+ }
+ }
+
+ return addr
+}
+
+// GetSourceIP retrieves the IP from the request headers
+// and falls back to r.RemoteAddr when necessary.
+func GetSourceIP(r *http.Request) string {
+ addr := GetSourceIPFromHeaders(r)
+ if addr != "" {
+ return addr
+ }
+
+ // Default to remote address if headers not set.
+ addr, _, _ = net.SplitHostPort(r.RemoteAddr)
+ return addr
+}
diff --git a/api/minio/reqinfo.go b/api/minio/reqinfo.go
new file mode 100644
index 000000000..56e8c3ea8
--- /dev/null
+++ b/api/minio/reqinfo.go
@@ -0,0 +1,144 @@
+// Copyright (c) 2015-2021 MinIO, Inc.
+//
+// This file is part of MinIO Object Storage stack
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package logger
+
+import (
+ "context"
+ "fmt"
+ "sync"
+)
+
+// Key used for Get/SetReqInfo
+type contextKeyType string
+
+const contextLogKey = contextKeyType("miniolog")
+
+// KeyVal - appended to ReqInfo.Tags
+type KeyVal struct {
+ Key string
+ Val interface{}
+}
+
+// ReqInfo stores the request info.
+type ReqInfo struct {
+ RemoteHost string // Client Host/IP
+ Host string // Node Host/IP
+ UserAgent string // User Agent
+ DeploymentID string // x-minio-deployment-id
+ RequestID string // x-amz-request-id
+ API string // API name - GetObject PutObject NewMultipartUpload etc.
+ BucketName string // Bucket name
+ ObjectName string // Object name
+ AccessKey string // Access Key
+ tags []KeyVal // Any additional info not accommodated by above fields
+ sync.RWMutex
+}
+
+// NewReqInfo :
+func NewReqInfo(remoteHost, userAgent, deploymentID, requestID, api, bucket, object string) *ReqInfo {
+ req := ReqInfo{}
+ req.RemoteHost = remoteHost
+ req.UserAgent = userAgent
+ req.API = api
+ req.DeploymentID = deploymentID
+ req.RequestID = requestID
+ req.BucketName = bucket
+ req.ObjectName = object
+ return &req
+}
+
+// AppendTags - appends key/val to ReqInfo.tags
+func (r *ReqInfo) AppendTags(key string, val interface{}) *ReqInfo {
+ if r == nil {
+ return nil
+ }
+ r.Lock()
+ defer r.Unlock()
+ r.tags = append(r.tags, KeyVal{key, val})
+ return r
+}
+
+// SetTags - sets key/val to ReqInfo.tags
+func (r *ReqInfo) SetTags(key string, val interface{}) *ReqInfo {
+ if r == nil {
+ return nil
+ }
+ r.Lock()
+ defer r.Unlock()
+ // Search of tag key already exists in tags
+ var updated bool
+ for _, tag := range r.tags {
+ if tag.Key == key {
+ tag.Val = val
+ updated = true
+ break
+ }
+ }
+ if !updated {
+ // Append to the end of tags list
+ r.tags = append(r.tags, KeyVal{key, val})
+ }
+ return r
+}
+
+// GetTags - returns the user defined tags
+func (r *ReqInfo) GetTags() []KeyVal {
+ if r == nil {
+ return nil
+ }
+ r.RLock()
+ defer r.RUnlock()
+ return append([]KeyVal(nil), r.tags...)
+}
+
+// GetTagsMap - returns the user defined tags in a map structure
+func (r *ReqInfo) GetTagsMap() map[string]interface{} {
+ if r == nil {
+ return nil
+ }
+ r.RLock()
+ defer r.RUnlock()
+ m := make(map[string]interface{}, len(r.tags))
+ for _, t := range r.tags {
+ m[t.Key] = t.Val
+ }
+ return m
+}
+
+// SetReqInfo sets ReqInfo in the context.
+func SetReqInfo(ctx context.Context, req *ReqInfo) context.Context {
+ if ctx == nil {
+ LogIf(context.Background(), fmt.Errorf("context is nil"))
+ return nil
+ }
+ return context.WithValue(ctx, contextLogKey, req)
+}
+
+// GetReqInfo returns ReqInfo if set.
+func GetReqInfo(ctx context.Context) *ReqInfo {
+ if ctx != nil {
+ r, ok := ctx.Value(contextLogKey).(*ReqInfo)
+ if ok {
+ return r
+ }
+ r = &ReqInfo{}
+ SetReqInfo(ctx, r)
+ return r
+ }
+ return nil
+}