api-stat.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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. "context"
  20. "net/http"
  21. "strconv"
  22. "strings"
  23. "time"
  24. "github.com/minio/minio-go/v6/pkg/s3utils"
  25. )
  26. // BucketExists verify if bucket exists and you have permission to access it.
  27. func (c Client) BucketExists(bucketName string) (bool, http.Header, error) {
  28. // Input validation.
  29. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  30. return false, nil, err
  31. }
  32. // Execute HEAD on bucketName.
  33. resp, err := c.executeMethod(context.Background(), "HEAD", requestMetadata{
  34. bucketName: bucketName,
  35. contentSHA256Hex: emptySHA256Hex,
  36. })
  37. defer closeResponse(resp)
  38. if err != nil {
  39. if ToErrorResponse(err).Code == "NoSuchBucket" {
  40. return false, nil, nil
  41. }
  42. return false, nil, err
  43. }
  44. if resp != nil {
  45. resperr := httpRespToErrorResponse(resp, bucketName, "")
  46. if ToErrorResponse(resperr).Code == "NoSuchBucket" {
  47. return false, nil, nil
  48. }
  49. if resp.StatusCode != http.StatusOK {
  50. return false, nil, httpRespToErrorResponse(resp, bucketName, "")
  51. }
  52. }
  53. return true, resp.Header, nil
  54. }
  55. // List of header keys to be filtered, usually
  56. // from all S3 API http responses.
  57. var defaultFilterKeys = []string{
  58. "Connection",
  59. "Transfer-Encoding",
  60. "Accept-Ranges",
  61. "Date",
  62. "Server",
  63. "Vary",
  64. "x-amz-bucket-region",
  65. "x-amz-request-id",
  66. "x-amz-id-2",
  67. "Content-Security-Policy",
  68. "X-Xss-Protection",
  69. // Add new headers to be ignored.
  70. }
  71. // Extract only necessary metadata header key/values by
  72. // filtering them out with a list of custom header keys.
  73. func extractObjMetadata(header http.Header) http.Header {
  74. filterKeys := append([]string{
  75. "ETag",
  76. "Content-Length",
  77. "Last-Modified",
  78. "Content-Type",
  79. "Expires",
  80. }, defaultFilterKeys...)
  81. return filterHeader(header, filterKeys)
  82. }
  83. // StatObject verifies if object exists and you have permission to access.
  84. func (c Client) StatObject(bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) {
  85. // Input validation.
  86. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  87. return ObjectInfo{}, err
  88. }
  89. if err := s3utils.CheckValidObjectName(objectName); err != nil {
  90. return ObjectInfo{}, err
  91. }
  92. return c.statObject(context.Background(), bucketName, objectName, opts)
  93. }
  94. // Lower level API for statObject supporting pre-conditions and range headers.
  95. func (c Client) statObject(ctx context.Context, bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) {
  96. // Input validation.
  97. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  98. return ObjectInfo{}, err
  99. }
  100. if err := s3utils.CheckValidObjectName(objectName); err != nil {
  101. return ObjectInfo{}, err
  102. }
  103. // Execute HEAD on objectName.
  104. resp, err := c.executeMethod(ctx, "HEAD", requestMetadata{
  105. bucketName: bucketName,
  106. objectName: objectName,
  107. contentSHA256Hex: emptySHA256Hex,
  108. customHeader: opts.Header(),
  109. })
  110. defer closeResponse(resp)
  111. if err != nil {
  112. return ObjectInfo{}, err
  113. }
  114. if resp != nil {
  115. if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
  116. return ObjectInfo{}, httpRespToErrorResponse(resp, bucketName, objectName)
  117. }
  118. }
  119. // Trim off the odd double quotes from ETag in the beginning and end.
  120. md5sum := strings.TrimPrefix(resp.Header.Get("ETag"), "\"")
  121. md5sum = strings.TrimSuffix(md5sum, "\"")
  122. // Parse content length is exists
  123. var size int64 = -1
  124. contentLengthStr := resp.Header.Get("Content-Length")
  125. if contentLengthStr != "" {
  126. size, err = strconv.ParseInt(contentLengthStr, 10, 64)
  127. if err != nil {
  128. // Content-Length is not valid
  129. return ObjectInfo{}, ErrorResponse{
  130. Code: "InternalError",
  131. Message: "Content-Length is invalid. " + reportIssue,
  132. BucketName: bucketName,
  133. Key: objectName,
  134. RequestID: resp.Header.Get("x-amz-request-id"),
  135. HostID: resp.Header.Get("x-amz-id-2"),
  136. Region: resp.Header.Get("x-amz-bucket-region"),
  137. }
  138. }
  139. }
  140. // Parse Last-Modified has http time format.
  141. date, err := time.Parse(http.TimeFormat, resp.Header.Get("Last-Modified"))
  142. if err != nil {
  143. return ObjectInfo{}, ErrorResponse{
  144. Code: "InternalError",
  145. Message: "Last-Modified time format is invalid. " + reportIssue,
  146. BucketName: bucketName,
  147. Key: objectName,
  148. RequestID: resp.Header.Get("x-amz-request-id"),
  149. HostID: resp.Header.Get("x-amz-id-2"),
  150. Region: resp.Header.Get("x-amz-bucket-region"),
  151. }
  152. }
  153. // Fetch content type if any present.
  154. contentType := strings.TrimSpace(resp.Header.Get("Content-Type"))
  155. if contentType == "" {
  156. contentType = "application/octet-stream"
  157. }
  158. expiryStr := resp.Header.Get("Expires")
  159. var expTime time.Time
  160. if t, err := time.Parse(http.TimeFormat, expiryStr); err == nil {
  161. expTime = t.UTC()
  162. }
  163. // Save object metadata info.
  164. return ObjectInfo{
  165. ETag: md5sum,
  166. Key: objectName,
  167. Size: size,
  168. LastModified: date,
  169. ContentType: contentType,
  170. Expires: expTime,
  171. // Extract only the relevant header keys describing the object.
  172. // following function filters out a list of standard set of keys
  173. // which are not part of object metadata.
  174. Metadata: extractObjMetadata(resp.Header),
  175. }, nil
  176. }