bucket-cache.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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. "net"
  20. "net/http"
  21. "net/url"
  22. "path"
  23. "sync"
  24. "github.com/minio/minio-go/v6/pkg/credentials"
  25. "github.com/minio/minio-go/v6/pkg/s3signer"
  26. "github.com/minio/minio-go/v6/pkg/s3utils"
  27. )
  28. // bucketLocationCache - Provides simple mechanism to hold bucket
  29. // locations in memory.
  30. type bucketLocationCache struct {
  31. // mutex is used for handling the concurrent
  32. // read/write requests for cache.
  33. sync.RWMutex
  34. // items holds the cached bucket locations.
  35. items map[string]string
  36. }
  37. // newBucketLocationCache - Provides a new bucket location cache to be
  38. // used internally with the client object.
  39. func newBucketLocationCache() *bucketLocationCache {
  40. return &bucketLocationCache{
  41. items: make(map[string]string),
  42. }
  43. }
  44. // Get - Returns a value of a given key if it exists.
  45. func (r *bucketLocationCache) Get(bucketName string) (location string, ok bool) {
  46. r.RLock()
  47. defer r.RUnlock()
  48. location, ok = r.items[bucketName]
  49. return
  50. }
  51. // Set - Will persist a value into cache.
  52. func (r *bucketLocationCache) Set(bucketName string, location string) {
  53. r.Lock()
  54. defer r.Unlock()
  55. r.items[bucketName] = location
  56. }
  57. // Delete - Deletes a bucket name from cache.
  58. func (r *bucketLocationCache) Delete(bucketName string) {
  59. r.Lock()
  60. defer r.Unlock()
  61. delete(r.items, bucketName)
  62. }
  63. // GetBucketLocation - get location for the bucket name from location cache, if not
  64. // fetch freshly by making a new request.
  65. func (c Client) GetBucketLocation(bucketName string) (string, error) {
  66. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  67. return "", err
  68. }
  69. return c.getBucketLocation(bucketName)
  70. }
  71. // getBucketLocation - Get location for the bucketName from location map cache, if not
  72. // fetch freshly by making a new request.
  73. func (c Client) getBucketLocation(bucketName string) (string, error) {
  74. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  75. return "", err
  76. }
  77. // Region set then no need to fetch bucket location.
  78. if c.region != "" {
  79. return c.region, nil
  80. }
  81. if location, ok := c.bucketLocCache.Get(bucketName); ok {
  82. return location, nil
  83. }
  84. // Initialize a new request.
  85. req, err := c.getBucketLocationRequest(bucketName)
  86. if err != nil {
  87. return "", err
  88. }
  89. // Initiate the request.
  90. resp, err := c.do(req)
  91. defer closeResponse(resp)
  92. if err != nil {
  93. return "", err
  94. }
  95. location, err := processBucketLocationResponse(resp, bucketName)
  96. if err != nil {
  97. return "", err
  98. }
  99. c.bucketLocCache.Set(bucketName, location)
  100. return location, nil
  101. }
  102. // processes the getBucketLocation http response from the server.
  103. func processBucketLocationResponse(resp *http.Response, bucketName string) (bucketLocation string, err error) {
  104. if resp != nil {
  105. if resp.StatusCode != http.StatusOK {
  106. err = httpRespToErrorResponse(resp, bucketName, "")
  107. errResp := ToErrorResponse(err)
  108. // For access denied error, it could be an anonymous
  109. // request. Move forward and let the top level callers
  110. // succeed if possible based on their policy.
  111. switch errResp.Code {
  112. case "AuthorizationHeaderMalformed":
  113. fallthrough
  114. case "InvalidRegion":
  115. fallthrough
  116. case "AccessDenied":
  117. if errResp.Region == "" {
  118. return "us-east-1", nil
  119. }
  120. return errResp.Region, nil
  121. }
  122. return "", err
  123. }
  124. }
  125. // Extract location.
  126. var locationConstraint string
  127. err = xmlDecoder(resp.Body, &locationConstraint)
  128. if err != nil {
  129. return "", err
  130. }
  131. location := locationConstraint
  132. // Location is empty will be 'us-east-1'.
  133. if location == "" {
  134. location = "us-east-1"
  135. }
  136. // Location can be 'EU' convert it to meaningful 'eu-west-1'.
  137. if location == "EU" {
  138. location = "eu-west-1"
  139. }
  140. // Save the location into cache.
  141. // Return.
  142. return location, nil
  143. }
  144. // getBucketLocationRequest - Wrapper creates a new getBucketLocation request.
  145. func (c Client) getBucketLocationRequest(bucketName string) (*http.Request, error) {
  146. // Set location query.
  147. urlValues := make(url.Values)
  148. urlValues.Set("location", "")
  149. // Set get bucket location always as path style.
  150. targetURL := *c.endpointURL
  151. // as it works in makeTargetURL method from api.go file
  152. if h, p, err := net.SplitHostPort(targetURL.Host); err == nil {
  153. if targetURL.Scheme == "http" && p == "80" || targetURL.Scheme == "https" && p == "443" {
  154. targetURL.Host = h
  155. }
  156. }
  157. targetURL.Path = path.Join(bucketName, "") + "/"
  158. targetURL.RawQuery = urlValues.Encode()
  159. // Get a new HTTP request for the method.
  160. req, err := http.NewRequest("GET", targetURL.String(), nil)
  161. if err != nil {
  162. return nil, err
  163. }
  164. // Set UserAgent for the request.
  165. c.setUserAgent(req)
  166. // Get credentials from the configured credentials provider.
  167. value, err := c.credsProvider.Get()
  168. if err != nil {
  169. return nil, err
  170. }
  171. var (
  172. signerType = value.SignerType
  173. accessKeyID = value.AccessKeyID
  174. secretAccessKey = value.SecretAccessKey
  175. sessionToken = value.SessionToken
  176. )
  177. // Custom signer set then override the behavior.
  178. if c.overrideSignerType != credentials.SignatureDefault {
  179. signerType = c.overrideSignerType
  180. }
  181. // If signerType returned by credentials helper is anonymous,
  182. // then do not sign regardless of signerType override.
  183. if value.SignerType == credentials.SignatureAnonymous {
  184. signerType = credentials.SignatureAnonymous
  185. }
  186. if signerType.IsAnonymous() {
  187. return req, nil
  188. }
  189. if signerType.IsV2() {
  190. // Get Bucket Location calls should be always path style
  191. isVirtualHost := false
  192. req = s3signer.SignV2(*req, accessKeyID, secretAccessKey, isVirtualHost)
  193. return req, nil
  194. }
  195. // Set sha256 sum for signature calculation only with signature version '4'.
  196. contentSha256 := emptySHA256Hex
  197. if c.secure {
  198. contentSha256 = unsignedPayload
  199. }
  200. req.Header.Set("X-Amz-Content-Sha256", contentSha256)
  201. req = s3signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, "us-east-1")
  202. return req, nil
  203. }