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