gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

api-error-response.go (8025B)


      1 /*
      2  * MinIO Go Library for Amazon S3 Compatible Cloud Storage
      3  * Copyright 2015-2020 MinIO, Inc.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package minio
     19 
     20 import (
     21 	"bytes"
     22 	"encoding/xml"
     23 	"fmt"
     24 	"io"
     25 	"net/http"
     26 )
     27 
     28 /* **** SAMPLE ERROR RESPONSE ****
     29 <?xml version="1.0" encoding="UTF-8"?>
     30 <Error>
     31    <Code>AccessDenied</Code>
     32    <Message>Access Denied</Message>
     33    <BucketName>bucketName</BucketName>
     34    <Key>objectName</Key>
     35    <RequestId>F19772218238A85A</RequestId>
     36    <HostId>GuWkjyviSiGHizehqpmsD1ndz5NClSP19DOT+s2mv7gXGQ8/X1lhbDGiIJEXpGFD</HostId>
     37 </Error>
     38 */
     39 
     40 // ErrorResponse - Is the typed error returned by all API operations.
     41 // ErrorResponse struct should be comparable since it is compared inside
     42 // golang http API (https://github.com/golang/go/issues/29768)
     43 type ErrorResponse struct {
     44 	XMLName    xml.Name `xml:"Error" json:"-"`
     45 	Code       string
     46 	Message    string
     47 	BucketName string
     48 	Key        string
     49 	Resource   string
     50 	RequestID  string `xml:"RequestId"`
     51 	HostID     string `xml:"HostId"`
     52 
     53 	// Region where the bucket is located. This header is returned
     54 	// only in HEAD bucket and ListObjects response.
     55 	Region string
     56 
     57 	// Captures the server string returned in response header.
     58 	Server string
     59 
     60 	// Underlying HTTP status code for the returned error
     61 	StatusCode int `xml:"-" json:"-"`
     62 }
     63 
     64 // ToErrorResponse - Returns parsed ErrorResponse struct from body and
     65 // http headers.
     66 //
     67 // For example:
     68 //
     69 //	import s3 "github.com/minio/minio-go/v7"
     70 //	...
     71 //	...
     72 //	reader, stat, err := s3.GetObject(...)
     73 //	if err != nil {
     74 //	   resp := s3.ToErrorResponse(err)
     75 //	}
     76 //	...
     77 func ToErrorResponse(err error) ErrorResponse {
     78 	switch err := err.(type) {
     79 	case ErrorResponse:
     80 		return err
     81 	default:
     82 		return ErrorResponse{}
     83 	}
     84 }
     85 
     86 // Error - Returns S3 error string.
     87 func (e ErrorResponse) Error() string {
     88 	if e.Message == "" {
     89 		msg, ok := s3ErrorResponseMap[e.Code]
     90 		if !ok {
     91 			msg = fmt.Sprintf("Error response code %s.", e.Code)
     92 		}
     93 		return msg
     94 	}
     95 	return e.Message
     96 }
     97 
     98 // Common string for errors to report issue location in unexpected
     99 // cases.
    100 const (
    101 	reportIssue = "Please report this issue at https://github.com/minio/minio-go/issues."
    102 )
    103 
    104 // xmlDecodeAndBody reads the whole body up to 1MB and
    105 // tries to XML decode it into v.
    106 // The body that was read and any error from reading or decoding is returned.
    107 func xmlDecodeAndBody(bodyReader io.Reader, v interface{}) ([]byte, error) {
    108 	// read the whole body (up to 1MB)
    109 	const maxBodyLength = 1 << 20
    110 	body, err := io.ReadAll(io.LimitReader(bodyReader, maxBodyLength))
    111 	if err != nil {
    112 		return nil, err
    113 	}
    114 	return bytes.TrimSpace(body), xmlDecoder(bytes.NewReader(body), v)
    115 }
    116 
    117 // httpRespToErrorResponse returns a new encoded ErrorResponse
    118 // structure as error.
    119 func httpRespToErrorResponse(resp *http.Response, bucketName, objectName string) error {
    120 	if resp == nil {
    121 		msg := "Empty http response. " + reportIssue
    122 		return errInvalidArgument(msg)
    123 	}
    124 
    125 	errResp := ErrorResponse{
    126 		StatusCode: resp.StatusCode,
    127 		Server:     resp.Header.Get("Server"),
    128 	}
    129 
    130 	errBody, err := xmlDecodeAndBody(resp.Body, &errResp)
    131 	// Xml decoding failed with no body, fall back to HTTP headers.
    132 	if err != nil {
    133 		switch resp.StatusCode {
    134 		case http.StatusNotFound:
    135 			if objectName == "" {
    136 				errResp = ErrorResponse{
    137 					StatusCode: resp.StatusCode,
    138 					Code:       "NoSuchBucket",
    139 					Message:    "The specified bucket does not exist.",
    140 					BucketName: bucketName,
    141 				}
    142 			} else {
    143 				errResp = ErrorResponse{
    144 					StatusCode: resp.StatusCode,
    145 					Code:       "NoSuchKey",
    146 					Message:    "The specified key does not exist.",
    147 					BucketName: bucketName,
    148 					Key:        objectName,
    149 				}
    150 			}
    151 		case http.StatusForbidden:
    152 			errResp = ErrorResponse{
    153 				StatusCode: resp.StatusCode,
    154 				Code:       "AccessDenied",
    155 				Message:    "Access Denied.",
    156 				BucketName: bucketName,
    157 				Key:        objectName,
    158 			}
    159 		case http.StatusConflict:
    160 			errResp = ErrorResponse{
    161 				StatusCode: resp.StatusCode,
    162 				Code:       "Conflict",
    163 				Message:    "Bucket not empty.",
    164 				BucketName: bucketName,
    165 			}
    166 		case http.StatusPreconditionFailed:
    167 			errResp = ErrorResponse{
    168 				StatusCode: resp.StatusCode,
    169 				Code:       "PreconditionFailed",
    170 				Message:    s3ErrorResponseMap["PreconditionFailed"],
    171 				BucketName: bucketName,
    172 				Key:        objectName,
    173 			}
    174 		default:
    175 			msg := resp.Status
    176 			if len(errBody) > 0 {
    177 				msg = string(errBody)
    178 				if len(msg) > 1024 {
    179 					msg = msg[:1024] + "..."
    180 				}
    181 			}
    182 			errResp = ErrorResponse{
    183 				StatusCode: resp.StatusCode,
    184 				Code:       resp.Status,
    185 				Message:    msg,
    186 				BucketName: bucketName,
    187 			}
    188 		}
    189 	}
    190 
    191 	// Save hostID, requestID and region information
    192 	// from headers if not available through error XML.
    193 	if errResp.RequestID == "" {
    194 		errResp.RequestID = resp.Header.Get("x-amz-request-id")
    195 	}
    196 	if errResp.HostID == "" {
    197 		errResp.HostID = resp.Header.Get("x-amz-id-2")
    198 	}
    199 	if errResp.Region == "" {
    200 		errResp.Region = resp.Header.Get("x-amz-bucket-region")
    201 	}
    202 	if errResp.Code == "InvalidRegion" && errResp.Region != "" {
    203 		errResp.Message = fmt.Sprintf("Region does not match, expecting region ‘%s’.", errResp.Region)
    204 	}
    205 
    206 	return errResp
    207 }
    208 
    209 // errTransferAccelerationBucket - bucket name is invalid to be used with transfer acceleration.
    210 func errTransferAccelerationBucket(bucketName string) error {
    211 	return ErrorResponse{
    212 		StatusCode: http.StatusBadRequest,
    213 		Code:       "InvalidArgument",
    214 		Message:    "The name of the bucket used for Transfer Acceleration must be DNS-compliant and must not contain periods ‘.’.",
    215 		BucketName: bucketName,
    216 	}
    217 }
    218 
    219 // errEntityTooLarge - Input size is larger than supported maximum.
    220 func errEntityTooLarge(totalSize, maxObjectSize int64, bucketName, objectName string) error {
    221 	msg := fmt.Sprintf("Your proposed upload size ‘%d’ exceeds the maximum allowed object size ‘%d’ for single PUT operation.", totalSize, maxObjectSize)
    222 	return ErrorResponse{
    223 		StatusCode: http.StatusBadRequest,
    224 		Code:       "EntityTooLarge",
    225 		Message:    msg,
    226 		BucketName: bucketName,
    227 		Key:        objectName,
    228 	}
    229 }
    230 
    231 // errEntityTooSmall - Input size is smaller than supported minimum.
    232 func errEntityTooSmall(totalSize int64, bucketName, objectName string) error {
    233 	msg := fmt.Sprintf("Your proposed upload size ‘%d’ is below the minimum allowed object size ‘0B’ for single PUT operation.", totalSize)
    234 	return ErrorResponse{
    235 		StatusCode: http.StatusBadRequest,
    236 		Code:       "EntityTooSmall",
    237 		Message:    msg,
    238 		BucketName: bucketName,
    239 		Key:        objectName,
    240 	}
    241 }
    242 
    243 // errUnexpectedEOF - Unexpected end of file reached.
    244 func errUnexpectedEOF(totalRead, totalSize int64, bucketName, objectName string) error {
    245 	msg := fmt.Sprintf("Data read ‘%d’ is not equal to the size ‘%d’ of the input Reader.", totalRead, totalSize)
    246 	return ErrorResponse{
    247 		StatusCode: http.StatusBadRequest,
    248 		Code:       "UnexpectedEOF",
    249 		Message:    msg,
    250 		BucketName: bucketName,
    251 		Key:        objectName,
    252 	}
    253 }
    254 
    255 // errInvalidArgument - Invalid argument response.
    256 func errInvalidArgument(message string) error {
    257 	return ErrorResponse{
    258 		StatusCode: http.StatusBadRequest,
    259 		Code:       "InvalidArgument",
    260 		Message:    message,
    261 		RequestID:  "minio",
    262 	}
    263 }
    264 
    265 // errAPINotSupported - API not supported response
    266 // The specified API call is not supported
    267 func errAPINotSupported(message string) error {
    268 	return ErrorResponse{
    269 		StatusCode: http.StatusNotImplemented,
    270 		Code:       "APINotSupported",
    271 		Message:    message,
    272 		RequestID:  "minio",
    273 	}
    274 }