api-error-response.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. /*
  2. * MinIO Go Library for Amazon S3 Compatible Cloud Storage
  3. * Copyright 2015-2017 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 s3cli
  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. // Underlying HTTP status code for the returned error
  49. StatusCode int `xml:"-" json:"-"`
  50. }
  51. // ToErrorResponse - Returns parsed ErrorResponse struct from body and
  52. // http headers.
  53. //
  54. // For example:
  55. //
  56. // import s3 "github.com/minio/minio-go/v6"
  57. // ...
  58. // ...
  59. // reader, stat, err := s3.GetObject(...)
  60. // if err != nil {
  61. // resp := s3.ToErrorResponse(err)
  62. // }
  63. // ...
  64. func ToErrorResponse(err error) ErrorResponse {
  65. switch err := err.(type) {
  66. case ErrorResponse:
  67. return err
  68. default:
  69. return ErrorResponse{}
  70. }
  71. }
  72. // Error - Returns S3 error string.
  73. func (e ErrorResponse) Error() string {
  74. if e.Message == "" {
  75. msg, ok := s3ErrorResponseMap[e.Code]
  76. if !ok {
  77. msg = fmt.Sprintf("Error response code %s.", e.Code)
  78. }
  79. return msg
  80. }
  81. return e.Message
  82. }
  83. // Common string for errors to report issue location in unexpected
  84. // cases.
  85. const (
  86. reportIssue = "Please report this issue at https://github.com/minio/minio-go/issues."
  87. )
  88. // httpRespToErrorResponse returns a new encoded ErrorResponse
  89. // structure as error.
  90. func httpRespToErrorResponse(resp *http.Response, bucketName, objectName string) error {
  91. if resp == nil {
  92. msg := "Response is empty. " + reportIssue
  93. return ErrInvalidArgument(msg)
  94. }
  95. errResp := ErrorResponse{
  96. StatusCode: resp.StatusCode,
  97. }
  98. err := xmlDecoder(resp.Body, &errResp)
  99. // Xml decoding failed with no body, fall back to HTTP headers.
  100. if err != nil {
  101. switch resp.StatusCode {
  102. case http.StatusNotFound:
  103. if objectName == "" {
  104. errResp = ErrorResponse{
  105. StatusCode: resp.StatusCode,
  106. Code: "NoSuchBucket",
  107. Message: "The specified bucket does not exist.",
  108. BucketName: bucketName,
  109. }
  110. } else {
  111. errResp = ErrorResponse{
  112. StatusCode: resp.StatusCode,
  113. Code: "NoSuchKey",
  114. Message: "The specified key does not exist.",
  115. BucketName: bucketName,
  116. Key: objectName,
  117. }
  118. }
  119. case http.StatusForbidden:
  120. errResp = ErrorResponse{
  121. StatusCode: resp.StatusCode,
  122. Code: "AccessDenied",
  123. Message: "Access Denied.",
  124. BucketName: bucketName,
  125. Key: objectName,
  126. }
  127. case http.StatusConflict:
  128. errResp = ErrorResponse{
  129. StatusCode: resp.StatusCode,
  130. Code: "Conflict",
  131. Message: "Bucket not empty.",
  132. BucketName: bucketName,
  133. }
  134. case http.StatusPreconditionFailed:
  135. errResp = ErrorResponse{
  136. StatusCode: resp.StatusCode,
  137. Code: "PreconditionFailed",
  138. Message: s3ErrorResponseMap["PreconditionFailed"],
  139. BucketName: bucketName,
  140. Key: objectName,
  141. }
  142. default:
  143. errResp = ErrorResponse{
  144. StatusCode: resp.StatusCode,
  145. Code: resp.Status,
  146. Message: resp.Status,
  147. BucketName: bucketName,
  148. }
  149. }
  150. }
  151. // Save hostID, requestID and region information
  152. // from headers if not available through error XML.
  153. if errResp.RequestID == "" {
  154. errResp.RequestID = resp.Header.Get("x-amz-request-id")
  155. }
  156. if errResp.HostID == "" {
  157. errResp.HostID = resp.Header.Get("x-amz-id-2")
  158. }
  159. if errResp.Region == "" {
  160. errResp.Region = resp.Header.Get("x-amz-bucket-region")
  161. }
  162. if errResp.Code == "InvalidRegion" && errResp.Region != "" {
  163. errResp.Message = fmt.Sprintf("Region does not match, expecting region ‘%s’.", errResp.Region)
  164. }
  165. return errResp
  166. }
  167. // ErrTransferAccelerationBucket - bucket name is invalid to be used with transfer acceleration.
  168. func ErrTransferAccelerationBucket(bucketName string) error {
  169. return ErrorResponse{
  170. StatusCode: http.StatusBadRequest,
  171. Code: "InvalidArgument",
  172. Message: "The name of the bucket used for Transfer Acceleration must be DNS-compliant and must not contain periods ‘.’.",
  173. BucketName: bucketName,
  174. }
  175. }
  176. // ErrEntityTooLarge - Input size is larger than supported maximum.
  177. func ErrEntityTooLarge(totalSize, maxObjectSize int64, bucketName, objectName string) error {
  178. msg := fmt.Sprintf("Your proposed upload size ‘%d’ exceeds the maximum allowed object size ‘%d’ for single PUT operation.", totalSize, maxObjectSize)
  179. return ErrorResponse{
  180. StatusCode: http.StatusBadRequest,
  181. Code: "EntityTooLarge",
  182. Message: msg,
  183. BucketName: bucketName,
  184. Key: objectName,
  185. }
  186. }
  187. // ErrEntityTooSmall - Input size is smaller than supported minimum.
  188. func ErrEntityTooSmall(totalSize int64, bucketName, objectName string) error {
  189. msg := fmt.Sprintf("Your proposed upload size ‘%d’ is below the minimum allowed object size ‘0B’ for single PUT operation.", totalSize)
  190. return ErrorResponse{
  191. StatusCode: http.StatusBadRequest,
  192. Code: "EntityTooSmall",
  193. Message: msg,
  194. BucketName: bucketName,
  195. Key: objectName,
  196. }
  197. }
  198. // ErrUnexpectedEOF - Unexpected end of file reached.
  199. func ErrUnexpectedEOF(totalRead, totalSize int64, bucketName, objectName string) error {
  200. msg := fmt.Sprintf("Data read ‘%d’ is not equal to the size ‘%d’ of the input Reader.", totalRead, totalSize)
  201. return ErrorResponse{
  202. StatusCode: http.StatusBadRequest,
  203. Code: "UnexpectedEOF",
  204. Message: msg,
  205. BucketName: bucketName,
  206. Key: objectName,
  207. }
  208. }
  209. // ErrInvalidBucketName - Invalid bucket name response.
  210. func ErrInvalidBucketName(message string) error {
  211. return ErrorResponse{
  212. StatusCode: http.StatusBadRequest,
  213. Code: "InvalidBucketName",
  214. Message: message,
  215. RequestID: "minio",
  216. }
  217. }
  218. // ErrInvalidObjectName - Invalid object name response.
  219. func ErrInvalidObjectName(message string) error {
  220. return ErrorResponse{
  221. StatusCode: http.StatusNotFound,
  222. Code: "NoSuchKey",
  223. Message: message,
  224. RequestID: "minio",
  225. }
  226. }
  227. // ErrInvalidObjectPrefix - Invalid object prefix response is
  228. // similar to object name response.
  229. var ErrInvalidObjectPrefix = ErrInvalidObjectName
  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. // ErrNoSuchBucketPolicy - No Such Bucket Policy response
  240. // The specified bucket does not have a bucket policy.
  241. func ErrNoSuchBucketPolicy(message string) error {
  242. return ErrorResponse{
  243. StatusCode: http.StatusNotFound,
  244. Code: "NoSuchBucketPolicy",
  245. Message: message,
  246. RequestID: "minio",
  247. }
  248. }
  249. // ErrAPINotSupported - API not supported response
  250. // The specified API call is not supported
  251. func ErrAPINotSupported(message string) error {
  252. return ErrorResponse{
  253. StatusCode: http.StatusNotImplemented,
  254. Code: "APINotSupported",
  255. Message: message,
  256. RequestID: "minio",
  257. }
  258. }