mirror of
https://github.com/alist-org/gofakes3.git
synced 2025-12-24 12:58:04 +08:00
351 lines
10 KiB
Go
351 lines
10 KiB
Go
package gofakes3
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
xml "github.com/minio/xxml"
|
|
)
|
|
|
|
// Error codes are documented here:
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
|
//
|
|
// If you add a code to this list, please also add it to ErrorCode.Status().
|
|
const (
|
|
ErrNone ErrorCode = ""
|
|
|
|
// The Content-MD5 you specified did not match what we received.
|
|
ErrBadDigest ErrorCode = "BadDigest"
|
|
|
|
ErrBucketAlreadyExists ErrorCode = "BucketAlreadyExists"
|
|
|
|
// Raised when attempting to delete a bucket that still contains items.
|
|
ErrBucketNotEmpty ErrorCode = "BucketNotEmpty"
|
|
|
|
// "Indicates that the versioning configuration specified in the request is invalid"
|
|
ErrIllegalVersioningConfiguration ErrorCode = "IllegalVersioningConfigurationException"
|
|
|
|
// You did not provide the number of bytes specified by the Content-Length
|
|
// HTTP header:
|
|
ErrIncompleteBody ErrorCode = "IncompleteBody"
|
|
|
|
// POST requires exactly one file upload per request.
|
|
ErrIncorrectNumberOfFilesInPostRequest ErrorCode = "IncorrectNumberOfFilesInPostRequest"
|
|
|
|
// InlineDataTooLarge occurs when using the PutObjectInline method of the
|
|
// SOAP interface
|
|
// (https://docs.aws.amazon.com/AmazonS3/latest/API/SOAPPutObjectInline.html).
|
|
// This is not documented on the errors page; the error is included here
|
|
// only for reference.
|
|
ErrInlineDataTooLarge ErrorCode = "InlineDataTooLarge"
|
|
|
|
ErrInvalidArgument ErrorCode = "InvalidArgument"
|
|
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html#bucketnamingrules
|
|
ErrInvalidBucketName ErrorCode = "InvalidBucketName"
|
|
|
|
// The Content-MD5 you specified is not valid.
|
|
ErrInvalidDigest ErrorCode = "InvalidDigest"
|
|
|
|
ErrInvalidRange ErrorCode = "InvalidRange"
|
|
ErrInvalidToken ErrorCode = "InvalidToken"
|
|
ErrKeyTooLong ErrorCode = "KeyTooLongError" // This is not a typo: Error is part of the string, but redundant in the constant name
|
|
ErrMalformedPOSTRequest ErrorCode = "MalformedPOSTRequest"
|
|
|
|
// One or more of the specified parts could not be found. The part might
|
|
// not have been uploaded, or the specified entity tag might not have
|
|
// matched the part's entity tag.
|
|
ErrInvalidPart ErrorCode = "InvalidPart"
|
|
|
|
// The list of parts was not in ascending order. Parts list must be
|
|
// specified in order by part number.
|
|
ErrInvalidPartOrder ErrorCode = "InvalidPartOrder"
|
|
|
|
ErrInvalidURI ErrorCode = "InvalidURI"
|
|
|
|
ErrMetadataTooLarge ErrorCode = "MetadataTooLarge"
|
|
ErrMethodNotAllowed ErrorCode = "MethodNotAllowed"
|
|
ErrMalformedXML ErrorCode = "MalformedXML"
|
|
|
|
// You must provide the Content-Length HTTP header.
|
|
ErrMissingContentLength ErrorCode = "MissingContentLength"
|
|
|
|
// See BucketNotFound() for a helper function for this error:
|
|
ErrNoSuchBucket ErrorCode = "NoSuchBucket"
|
|
|
|
// See KeyNotFound() for a helper function for this error:
|
|
ErrNoSuchKey ErrorCode = "NoSuchKey"
|
|
|
|
// The specified multipart upload does not exist. The upload ID might be
|
|
// invalid, or the multipart upload might have been aborted or completed.
|
|
ErrNoSuchUpload ErrorCode = "NoSuchUpload"
|
|
|
|
ErrNoSuchVersion ErrorCode = "NoSuchVersion"
|
|
|
|
// No need to retransmit the object
|
|
ErrNotModified ErrorCode = "NotModified"
|
|
|
|
ErrRequestTimeTooSkewed ErrorCode = "RequestTimeTooSkewed"
|
|
ErrTooManyBuckets ErrorCode = "TooManyBuckets"
|
|
ErrNotImplemented ErrorCode = "NotImplemented"
|
|
|
|
ErrInternal ErrorCode = "InternalError"
|
|
)
|
|
|
|
// INTERNAL errors! These are not part of the S3 interface, they are codes
|
|
// we have declared ourselves. Should all map to a 500 status code:
|
|
const (
|
|
ErrInternalPageNotImplemented InternalErrorCode = "PaginationNotImplemented"
|
|
)
|
|
|
|
// errorResponse should be implemented by any type that needs to be handled by
|
|
// ensureErrorResponse.
|
|
type errorResponse interface {
|
|
Error
|
|
enrich(requestID string)
|
|
}
|
|
|
|
func ensureErrorResponse(err error, requestID string) Error {
|
|
switch err := err.(type) {
|
|
case errorResponse:
|
|
err.enrich(requestID)
|
|
return err
|
|
|
|
case ErrorCode:
|
|
return &ErrorResponse{
|
|
Code: err,
|
|
RequestID: requestID,
|
|
Message: string(err),
|
|
}
|
|
|
|
default:
|
|
return &ErrorResponse{
|
|
Code: ErrInternal,
|
|
Message: "Internal Error",
|
|
RequestID: requestID,
|
|
}
|
|
}
|
|
}
|
|
|
|
type Error interface {
|
|
error
|
|
ErrorCode() ErrorCode
|
|
}
|
|
|
|
// ErrorResponse is the base error type returned by S3 when any error occurs.
|
|
//
|
|
// Some errors contain their own additional fields in the response, for example
|
|
// ErrRequestTimeTooSkewed, which contains the server time and the skew limit.
|
|
// To create one of these responses, subclass it (but please don't export it):
|
|
//
|
|
// type notQuiteRightResponse struct {
|
|
// ErrorResponse
|
|
// ExtraField int
|
|
// }
|
|
//
|
|
// Next, create a constructor that populates the error. Interfaces won't work
|
|
// for this job as the error itself does double-duty as the XML response
|
|
// object. Fill the struct out however you please, but don't forget to assign
|
|
// Code and Message:
|
|
//
|
|
// func NotQuiteRight(at time.Time, max time.Duration) error {
|
|
// code := ErrNotQuiteRight
|
|
// return ¬QuiteRightResponse{
|
|
// ErrorResponse{Code: code, Message: code.Message()},
|
|
// 123456789,
|
|
// }
|
|
// }
|
|
type ErrorResponse struct {
|
|
XMLName xml.Name `xml:"Error"`
|
|
|
|
Code ErrorCode
|
|
Message string `xml:",omitempty"`
|
|
RequestID string `xml:"RequestId,omitempty"`
|
|
HostID string `xml:"HostId,omitempty"`
|
|
}
|
|
|
|
func (e *ErrorResponse) ErrorCode() ErrorCode { return e.Code }
|
|
|
|
func (e *ErrorResponse) Error() string {
|
|
return fmt.Sprintf("%s: %s", e.Code, e.Message)
|
|
}
|
|
|
|
func (r *ErrorResponse) enrich(requestID string) {
|
|
r.RequestID = requestID
|
|
}
|
|
|
|
func ErrorMessage(code ErrorCode, message string) error {
|
|
return &ErrorResponse{Code: code, Message: message}
|
|
}
|
|
|
|
func ErrorMessagef(code ErrorCode, message string, args ...interface{}) error {
|
|
return &ErrorResponse{Code: code, Message: fmt.Sprintf(message, args...)}
|
|
}
|
|
|
|
type ErrorInvalidArgumentResponse struct {
|
|
ErrorResponse
|
|
|
|
ArgumentName string `xml:"ArgumentName"`
|
|
ArgumentValue string `xml:"ArgumentValue"`
|
|
}
|
|
|
|
func ErrorInvalidArgument(name, value, message string) error {
|
|
return &ErrorInvalidArgumentResponse{
|
|
ErrorResponse: ErrorResponse{Code: ErrInvalidArgument, Message: message},
|
|
ArgumentName: name, ArgumentValue: value}
|
|
}
|
|
|
|
// ErrorCode represents an S3 error code, documented here:
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
|
type ErrorCode string
|
|
|
|
func (e ErrorCode) ErrorCode() ErrorCode { return e }
|
|
func (e ErrorCode) Error() string { return string(e) }
|
|
|
|
// InternalErrorCode represents an GoFakeS3 error code. It maps to ErrInternal
|
|
// when constructing a response.
|
|
type InternalErrorCode string
|
|
|
|
func (e InternalErrorCode) ErrorCode() ErrorCode { return ErrInternal }
|
|
func (e InternalErrorCode) Error() string { return string(ErrInternal) }
|
|
|
|
// Message tries to return the same string as S3 would return for the error
|
|
// response, when it is known, or nothing when it is not. If you see the status
|
|
// text for a code we don't have listed in here in the wild, please let us
|
|
// know!
|
|
func (e ErrorCode) Message() string {
|
|
switch e {
|
|
case ErrInvalidBucketName:
|
|
return `Bucket name must match the regex "^[a-zA-Z0-9.\-_]{1,255}$"`
|
|
case ErrNoSuchBucket:
|
|
return "The specified bucket does not exist"
|
|
case ErrRequestTimeTooSkewed:
|
|
return "The difference between the request time and the current time is too large"
|
|
case ErrMalformedXML:
|
|
return "The XML you provided was not well-formed or did not validate against our published schema"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func (e ErrorCode) Status() int {
|
|
switch e {
|
|
case ErrBucketAlreadyExists,
|
|
ErrBucketNotEmpty:
|
|
return http.StatusConflict
|
|
|
|
case ErrBadDigest,
|
|
ErrIllegalVersioningConfiguration,
|
|
ErrIncompleteBody,
|
|
ErrIncorrectNumberOfFilesInPostRequest,
|
|
ErrInlineDataTooLarge,
|
|
ErrInvalidArgument,
|
|
ErrInvalidBucketName,
|
|
ErrInvalidDigest,
|
|
ErrInvalidPart,
|
|
ErrInvalidPartOrder,
|
|
ErrInvalidToken,
|
|
ErrInvalidURI,
|
|
ErrKeyTooLong,
|
|
ErrMetadataTooLarge,
|
|
ErrMethodNotAllowed,
|
|
ErrMalformedPOSTRequest,
|
|
ErrMalformedXML,
|
|
ErrTooManyBuckets:
|
|
return http.StatusBadRequest
|
|
|
|
case ErrRequestTimeTooSkewed:
|
|
return http.StatusForbidden
|
|
|
|
case ErrInvalidRange:
|
|
return http.StatusRequestedRangeNotSatisfiable
|
|
|
|
case ErrNoSuchBucket,
|
|
ErrNoSuchKey,
|
|
ErrNoSuchUpload,
|
|
ErrNoSuchVersion:
|
|
return http.StatusNotFound
|
|
|
|
case ErrNotImplemented:
|
|
return http.StatusNotImplemented
|
|
|
|
case ErrNotModified:
|
|
return http.StatusNotModified
|
|
|
|
case ErrMissingContentLength:
|
|
return http.StatusLengthRequired
|
|
|
|
case ErrInternal:
|
|
return http.StatusInternalServerError
|
|
}
|
|
|
|
return http.StatusInternalServerError
|
|
}
|
|
|
|
// HasErrorCode asserts that the error has a specific error code:
|
|
//
|
|
// if HasErrorCode(err, ErrNoSuchBucket) {
|
|
// // handle condition
|
|
// }
|
|
//
|
|
// If err is nil and code is ErrNone, HasErrorCode returns true.
|
|
func HasErrorCode(err error, code ErrorCode) bool {
|
|
if err == nil && code == "" {
|
|
return true
|
|
}
|
|
s3err, ok := err.(interface{ ErrorCode() ErrorCode })
|
|
if !ok {
|
|
return false
|
|
}
|
|
return s3err.ErrorCode() == code
|
|
}
|
|
|
|
// IsAlreadyExists asserts that the error is a kind that indicates the resource
|
|
// already exists, similar to os.IsExist.
|
|
func IsAlreadyExists(err error) bool {
|
|
return HasErrorCode(err, ErrBucketAlreadyExists)
|
|
}
|
|
|
|
type resourceErrorResponse struct {
|
|
ErrorResponse
|
|
Resource string
|
|
}
|
|
|
|
var _ errorResponse = &resourceErrorResponse{}
|
|
|
|
func ResourceError(code ErrorCode, resource string) error {
|
|
return &resourceErrorResponse{
|
|
ErrorResponse{Code: code, Message: code.Message()},
|
|
resource,
|
|
}
|
|
}
|
|
|
|
func BucketNotFound(bucket string) error { return ResourceError(ErrNoSuchBucket, bucket) }
|
|
func KeyNotFound(key string) error { return ResourceError(ErrNoSuchKey, key) }
|
|
|
|
type requestTimeTooSkewedResponse struct {
|
|
ErrorResponse
|
|
ServerTime time.Time
|
|
MaxAllowedSkewMilliseconds durationAsMilliseconds
|
|
}
|
|
|
|
var _ errorResponse = &requestTimeTooSkewedResponse{}
|
|
|
|
func requestTimeTooSkewed(at time.Time, max time.Duration) error {
|
|
code := ErrRequestTimeTooSkewed
|
|
return &requestTimeTooSkewedResponse{
|
|
ErrorResponse{Code: code, Message: code.Message()},
|
|
at, durationAsMilliseconds(max),
|
|
}
|
|
}
|
|
|
|
// durationAsMilliseconds tricks xml.Marshal into serialising a time.Duration as
|
|
// truncated milliseconds instead of nanoseconds.
|
|
type durationAsMilliseconds time.Duration
|
|
|
|
func (m durationAsMilliseconds) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
var s = fmt.Sprintf("%d", time.Duration(m)/time.Millisecond)
|
|
return e.EncodeElement(s, start)
|
|
}
|