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 }