api-error-response.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  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. package minio
  18. import (
  19. "encoding/xml"
  20. "fmt"
  21. "net/http"
  22. )
  23. /* **** SAMPLE ERROR RESPONSE ****
  24. <?xml version="1.0" encoding="UTF-8"?>
  25. <Error>
  26. <Code>AccessDenied</Code>
  27. <Message>Access Denied</Message>
  28. <BucketName>bucketName</BucketName>
  29. <Key>objectName</Key>
  30. <RequestId>F19772218238A85A</RequestId>
  31. <HostId>GuWkjyviSiGHizehqpmsD1ndz5NClSP19DOT+s2mv7gXGQ8/X1lhbDGiIJEXpGFD</HostId>
  32. </Error>
  33. */
  34. // ErrorResponse - Is the typed error returned by all API operations.
  35. // ErrorResponse struct should be comparable since it is compared inside
  36. // golang http API (https://github.com/golang/go/issues/29768)
  37. type ErrorResponse struct {
  38. XMLName xml.Name `xml:"Error" json:"-"`
  39. Code string
  40. Message string
  41. BucketName string
  42. Key string
  43. RequestID string `xml:"RequestId"`
  44. HostID string `xml:"HostId"`
  45. // Region where the bucket is located. This header is returned
  46. // only in HEAD bucket and ListObjects response.
  47. Region string
  48. // Captures the server string returned in response header.
  49. Server string
  50. // Underlying HTTP status code for the returned error
  51. StatusCode int `xml:"-" json:"-"`
  52. }
  53. // ToErrorResponse - Returns parsed ErrorResponse struct from body and
  54. // http headers.
  55. //
  56. // For example:
  57. //
  58. // import s3 "github.com/minio/minio-go/v7"
  59. // ...
  60. // ...
  61. // reader, stat, err := s3.GetObject(...)
  62. // if err != nil {
  63. // resp := s3.ToErrorResponse(err)
  64. // }
  65. // ...
  66. func ToErrorResponse(err error) ErrorResponse {
  67. switch err := err.(type) {
  68. case ErrorResponse:
  69. return err
  70. default:
  71. return ErrorResponse{}
  72. }
  73. }
  74. // Error - Returns S3 error string.
  75. func (e ErrorResponse) Error() string {
  76. if e.Message == "" {
  77. msg, ok := s3ErrorResponseMap[e.Code]
  78. if !ok {
  79. msg = fmt.Sprintf("Error response code %s.", e.Code)
  80. }
  81. return msg
  82. }
  83. return e.Message
  84. }
  85. // Common string for errors to report issue location in unexpected
  86. // cases.
  87. const (
  88. reportIssue = "Please report this issue at https://github.com/minio/minio-go/issues."
  89. )
  90. // httpRespToErrorResponse returns a new encoded ErrorResponse
  91. // structure as error.
  92. func httpRespToErrorResponse(resp *http.Response, bucketName, objectName string) error {
  93. if resp == nil {
  94. msg := "Empty http response. " + reportIssue
  95. return errInvalidArgument(msg)
  96. }
  97. errResp := ErrorResponse{
  98. StatusCode: resp.StatusCode,
  99. Server: resp.Header.Get("Server"),
  100. }
  101. err := xmlDecoder(resp.Body, &errResp)
  102. // Xml decoding failed with no body, fall back to HTTP headers.
  103. if err != nil {
  104. switch resp.StatusCode {
  105. case http.StatusNotFound:
  106. if objectName == "" {
  107. errResp = ErrorResponse{
  108. StatusCode: resp.StatusCode,
  109. Code: "NoSuchBucket",
  110. Message: "The specified bucket does not exist.",
  111. BucketName: bucketName,
  112. }
  113. } else {
  114. errResp = ErrorResponse{
  115. StatusCode: resp.StatusCode,
  116. Code: "NoSuchKey",
  117. Message: "The specified key does not exist.",
  118. BucketName: bucketName,
  119. Key: objectName,
  120. }
  121. }
  122. case http.StatusForbidden:
  123. errResp = ErrorResponse{
  124. StatusCode: resp.StatusCode,
  125. Code: "AccessDenied",
  126. Message: "Access Denied.",
  127. BucketName: bucketName,
  128. Key: objectName,
  129. }
  130. case http.StatusConflict:
  131. errResp = ErrorResponse{
  132. StatusCode: resp.StatusCode,
  133. Code: "Conflict",
  134. Message: "Bucket not empty.",
  135. BucketName: bucketName,
  136. }
  137. case http.StatusPreconditionFailed:
  138. errResp = ErrorResponse{
  139. StatusCode: resp.StatusCode,
  140. Code: "PreconditionFailed",
  141. Message: s3ErrorResponseMap["PreconditionFailed"],
  142. BucketName: bucketName,
  143. Key: objectName,
  144. }
  145. default:
  146. errResp = ErrorResponse{
  147. StatusCode: resp.StatusCode,
  148. Code: resp.Status,
  149. Message: resp.Status,
  150. BucketName: bucketName,
  151. }
  152. }
  153. }
  154. // Save hostID, requestID and region information
  155. // from headers if not available through error XML.
  156. if errResp.RequestID == "" {
  157. errResp.RequestID = resp.Header.Get("x-amz-request-id")
  158. }
  159. if errResp.HostID == "" {
  160. errResp.HostID = resp.Header.Get("x-amz-id-2")
  161. }
  162. if errResp.Region == "" {
  163. errResp.Region = resp.Header.Get("x-amz-bucket-region")
  164. }
  165. if errResp.Code == "InvalidRegion" && errResp.Region != "" {
  166. errResp.Message = fmt.Sprintf("Region does not match, expecting region ‘%s’.", errResp.Region)
  167. }
  168. return errResp
  169. }
  170. // errTransferAccelerationBucket - bucket name is invalid to be used with transfer acceleration.
  171. func errTransferAccelerationBucket(bucketName string) error {
  172. return ErrorResponse{
  173. StatusCode: http.StatusBadRequest,
  174. Code: "InvalidArgument",
  175. Message: "The name of the bucket used for Transfer Acceleration must be DNS-compliant and must not contain periods ‘.’.",
  176. BucketName: bucketName,
  177. }
  178. }
  179. // errEntityTooLarge - Input size is larger than supported maximum.
  180. func errEntityTooLarge(totalSize, maxObjectSize int64, bucketName, objectName string) error {
  181. msg := fmt.Sprintf("Your proposed upload size ‘%d’ exceeds the maximum allowed object size ‘%d’ for single PUT operation.", totalSize, maxObjectSize)
  182. return ErrorResponse{
  183. StatusCode: http.StatusBadRequest,
  184. Code: "EntityTooLarge",
  185. Message: msg,
  186. BucketName: bucketName,
  187. Key: objectName,
  188. }
  189. }
  190. // errEntityTooSmall - Input size is smaller than supported minimum.
  191. func errEntityTooSmall(totalSize int64, bucketName, objectName string) error {
  192. msg := fmt.Sprintf("Your proposed upload size ‘%d’ is below the minimum allowed object size ‘0B’ for single PUT operation.", totalSize)
  193. return ErrorResponse{
  194. StatusCode: http.StatusBadRequest,
  195. Code: "EntityTooSmall",
  196. Message: msg,
  197. BucketName: bucketName,
  198. Key: objectName,
  199. }
  200. }
  201. // errUnexpectedEOF - Unexpected end of file reached.
  202. func errUnexpectedEOF(totalRead, totalSize int64, bucketName, objectName string) error {
  203. msg := fmt.Sprintf("Data read ‘%d’ is not equal to the size ‘%d’ of the input Reader.", totalRead, totalSize)
  204. return ErrorResponse{
  205. StatusCode: http.StatusBadRequest,
  206. Code: "UnexpectedEOF",
  207. Message: msg,
  208. BucketName: bucketName,
  209. Key: objectName,
  210. }
  211. }
  212. // errInvalidBucketName - Invalid bucket name response.
  213. func errInvalidBucketName(message string) error {
  214. return ErrorResponse{
  215. StatusCode: http.StatusBadRequest,
  216. Code: "InvalidBucketName",
  217. Message: message,
  218. RequestID: "minio",
  219. }
  220. }
  221. // errInvalidObjectName - Invalid object name response.
  222. func errInvalidObjectName(message string) error {
  223. return ErrorResponse{
  224. StatusCode: http.StatusNotFound,
  225. Code: "NoSuchKey",
  226. Message: message,
  227. RequestID: "minio",
  228. }
  229. }
  230. // errInvalidArgument - Invalid argument response.
  231. func errInvalidArgument(message string) error {
  232. return ErrorResponse{
  233. StatusCode: http.StatusBadRequest,
  234. Code: "InvalidArgument",
  235. Message: message,
  236. RequestID: "minio",
  237. }
  238. }
  239. // errAPINotSupported - API not supported response
  240. // The specified API call is not supported
  241. func errAPINotSupported(message string) error {
  242. return ErrorResponse{
  243. StatusCode: http.StatusNotImplemented,
  244. Code: "APINotSupported",
  245. Message: message,
  246. RequestID: "minio",
  247. }
  248. }