/* * Minio Cloud Storage, (C) 2015 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cmd import ( "encoding/xml" "net/http" ) // 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 BucketName string Resource string RequestID string `xml:"RequestId"` HostID string `xml:"HostId"` } // APIErrorCode type of error status. type APIErrorCode int // Error codes, non exhaustive list - http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html const ( ErrNone APIErrorCode = iota ErrAccessDenied ErrBadDigest ErrBucketAlreadyExists ErrEntityTooSmall ErrEntityTooLarge ErrIncompleteBody ErrInternalError ErrInvalidAccessKeyID ErrInvalidBucketName ErrInvalidDigest ErrInvalidRange ErrInvalidMaxKeys ErrInvalidMaxUploads ErrInvalidMaxParts ErrInvalidPartNumberMarker ErrInvalidRequestBody ErrInvalidCopySource ErrInvalidCopyDest ErrInvalidPolicyDocument ErrMalformedXML ErrMissingContentLength ErrMissingContentMD5 ErrMissingRequestBodyError ErrNoSuchBucket ErrNoSuchBucketPolicy ErrNoSuchKey ErrNoSuchUpload ErrNotImplemented ErrPreconditionFailed ErrRequestTimeTooSkewed ErrSignatureDoesNotMatch ErrMethodNotAllowed ErrInvalidPart ErrInvalidPartOrder ErrAuthorizationHeaderMalformed ErrMalformedPOSTRequest ErrSignatureVersionNotSupported ErrBucketNotEmpty ErrAllAccessDisabled ErrMalformedPolicy ErrMissingFields ErrMissingCredTag ErrCredMalformed ErrInvalidRegion ErrInvalidService ErrInvalidRequestVersion ErrMissingSignTag ErrMissingSignHeadersTag ErrPolicyAlreadyExpired ErrMalformedDate ErrMalformedPresignedDate ErrMalformedCredentialDate ErrMalformedCredentialRegion ErrMalformedExpires ErrNegativeExpires ErrAuthHeaderEmpty ErrExpiredPresignRequest ErrRequestNotReadyYet ErrUnsignedHeaders ErrMissingDateHeader ErrInvalidQuerySignatureAlgo ErrInvalidQueryParams ErrBucketAlreadyOwnedByYou // Add new error codes here. // Bucket notification related errors. ErrEventNotification ErrARNNotification ErrRegionNotification ErrOverlappingFilterNotification ErrFilterNameInvalid ErrFilterNamePrefix ErrFilterNameSuffix ErrFilterValueInvalid ErrOverlappingConfigs // S3 extended errors. ErrContentSHA256Mismatch // Add new extended error codes here. // Minio extended errors. ErrReadQuorum ErrWriteQuorum ErrStorageFull ErrObjectExistsAsDirectory ErrPolicyNesting ErrInvalidObjectName ErrServerNotInitialized // Add new extended error codes here. // Please open a https://github.com/minio/minio/issues before adding // new error codes here. ) // error code to APIError structure, these fields carry respective // descriptions for all the error responses. var errorCodeResponse = map[APIErrorCode]APIError{ ErrInvalidCopyDest: { Code: "InvalidRequest", Description: "This copy request is illegal because it is trying to copy an object to itself.", HTTPStatusCode: http.StatusBadRequest, }, ErrInvalidCopySource: { Code: "InvalidArgument", Description: "Copy Source must mention the source bucket and key: sourcebucket/sourcekey.", 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, }, 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, }, 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, }, ErrBucketAlreadyExists: { Code: "BucketAlreadyExists", Description: "The requested bucket name is not available.", HTTPStatusCode: http.StatusConflict, }, 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, }, 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, }, 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, }, 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, }, 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, }, 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.", 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, }, 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, }, 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, }, 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, }, // FIXME: Should contain the invalid param set as seen in https://github.com/minio/minio/issues/2385. // right Description: "Error parsing the X-Amz-Credential parameter; incorrect date format \"%s\". This date in the credential must be in the format \"yyyyMMdd\".", // Need changes to make sure variable messages can be constructed. ErrMalformedCredentialDate: { Code: "AuthorizationQueryParametersError", Description: "Error parsing the X-Amz-Credential parameter; incorrect date format \"%s\". This date in the credential must be in the format \"yyyyMMdd\".", HTTPStatusCode: http.StatusBadRequest, }, // FIXME: Should contain the invalid param set as seen in https://github.com/minio/minio/issues/2385. // right Description: "Error parsing the X-Amz-Credential parameter; the region 'us-east-' is wrong; expecting 'us-east-1'". // Need changes to make sure variable messages can be constructed. ErrMalformedCredentialRegion: { Code: "AuthorizationQueryParametersError", Description: "Error parsing the X-Amz-Credential parameter; the region is wrong;", HTTPStatusCode: http.StatusBadRequest, }, ErrInvalidRegion: { Code: "InvalidRegion", Description: "Region does not match.", HTTPStatusCode: http.StatusBadRequest, }, // FIXME: Should contain the invalid param set as seen in https://github.com/minio/minio/issues/2385. // right Description: "Error parsing the X-Amz-Credential parameter; incorrect service \"s4\". This endpoint belongs to \"s3\".". // Need changes to make sure variable messages can be constructed. ErrInvalidService: { Code: "AuthorizationQueryParametersError", Description: "Error parsing the X-Amz-Credential parameter; incorrect service. This endpoint belongs to \"s3\".", HTTPStatusCode: http.StatusBadRequest, }, // FIXME: Should contain the invalid param set as seen in https://github.com/minio/minio/issues/2385. // Description: "Error parsing the X-Amz-Credential parameter; incorrect terminal "aws4_reque". This endpoint uses "aws4_request". // Need changes to make sure variable messages can be constructed. 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, }, ErrPolicyAlreadyExpired: { Code: "AccessDenied", Description: "Invalid according to Policy: Policy expired.", 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, }, // FIXME: Actual XML error response also contains the header which missed in list of signed header parameters. 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, }, /// 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, }, /// 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 few objects to proceed.", HTTPStatusCode: http.StatusInternalServerError, }, ErrObjectExistsAsDirectory: { Code: "XMinioObjectExistsAsDirectory", Description: "Object name already exists as a directory.", HTTPStatusCode: http.StatusConflict, }, ErrReadQuorum: { Code: "XMinioReadQuorum", Description: "Multiple disk failures, unable to reconstruct data.", HTTPStatusCode: http.StatusServiceUnavailable, }, ErrWriteQuorum: { Code: "XMinioWriteQuorum", Description: "Multiple disks failures, unable to write data.", HTTPStatusCode: http.StatusServiceUnavailable, }, ErrPolicyNesting: { Code: "XMinioPolicyNesting", Description: "Policy nesting conflict has occurred.", HTTPStatusCode: http.StatusConflict, }, ErrInvalidObjectName: { Code: "XMinioInvalidObjectName", Description: "Object name contains unsupported characters. Unsupported characters are `^*|\\\"", HTTPStatusCode: http.StatusBadRequest, }, ErrServerNotInitialized: { Code: "XMinioServerNotInitialized", Description: "Server not initialized, please try again.", HTTPStatusCode: http.StatusServiceUnavailable, }, // 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(err error) (apiErr APIErrorCode) { if err == nil { return ErrNone } err = errorCause(err) // Verify if the underlying error is signature mismatch. switch err { case errSignatureMismatch: apiErr = ErrSignatureDoesNotMatch case errContentSHA256Mismatch: apiErr = ErrContentSHA256Mismatch } if apiErr != ErrNone { // If there was a match in the above switch case. return apiErr } switch err.(type) { case StorageFull: apiErr = ErrStorageFull case BadDigest: apiErr = ErrBadDigest case IncompleteBody: apiErr = ErrIncompleteBody case ObjectExistsAsDirectory: apiErr = ErrObjectExistsAsDirectory case BucketNameInvalid: apiErr = ErrInvalidBucketName case BucketNotFound: apiErr = ErrNoSuchBucket case BucketNotEmpty: apiErr = ErrBucketNotEmpty case BucketExists: apiErr = ErrBucketAlreadyOwnedByYou case ObjectNotFound: apiErr = ErrNoSuchKey case ObjectNameInvalid: apiErr = ErrInvalidObjectName case InvalidUploadID: apiErr = ErrNoSuchUpload case InvalidPart: apiErr = ErrInvalidPart case InsufficientWriteQuorum: apiErr = ErrWriteQuorum case InsufficientReadQuorum: apiErr = ErrReadQuorum case PartTooSmall: apiErr = ErrEntityTooSmall default: apiErr = ErrInternalError } return apiErr } // getAPIError provides API Error for input API error code. func getAPIError(code APIErrorCode) APIError { return errorCodeResponse[code] } // getErrorResponse gets in standard error and resource value and // provides a encodable populated response values func getAPIErrorResponse(err APIError, resource string) APIErrorResponse { var data = APIErrorResponse{} data.Code = err.Code data.Message = err.Description if resource != "" { data.Resource = resource } // TODO implement this in future data.RequestID = "3L137" data.HostID = "3L137" return data } func getErrMalformedCredentialDate(malformedDateStr string) { }