/* * Minio Go Library for Amazon S3 Compatible Cloud Storage * Copyright 2015-2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package minio import ( "net/http" "net/url" "path" "sync" "github.com/minio/minio-go/pkg/credentials" "github.com/minio/minio-go/pkg/s3signer" "github.com/minio/minio-go/pkg/s3utils" ) // bucketLocationCache - Provides simple mechanism to hold bucket // locations in memory. type bucketLocationCache struct { // mutex is used for handling the concurrent // read/write requests for cache. sync.RWMutex // items holds the cached bucket locations. items map[string]string } // newBucketLocationCache - Provides a new bucket location cache to be // used internally with the client object. func newBucketLocationCache() *bucketLocationCache { return &bucketLocationCache{ items: make(map[string]string), } } // Get - Returns a value of a given key if it exists. func (r *bucketLocationCache) Get(bucketName string) (location string, ok bool) { r.RLock() defer r.RUnlock() location, ok = r.items[bucketName] return } // Set - Will persist a value into cache. func (r *bucketLocationCache) Set(bucketName string, location string) { r.Lock() defer r.Unlock() r.items[bucketName] = location } // Delete - Deletes a bucket name from cache. func (r *bucketLocationCache) Delete(bucketName string) { r.Lock() defer r.Unlock() delete(r.items, bucketName) } // GetBucketLocation - get location for the bucket name from location cache, if not // fetch freshly by making a new request. func (c Client) GetBucketLocation(bucketName string) (string, error) { if err := s3utils.CheckValidBucketName(bucketName); err != nil { return "", err } return c.getBucketLocation(bucketName) } // getBucketLocation - Get location for the bucketName from location map cache, if not // fetch freshly by making a new request. func (c Client) getBucketLocation(bucketName string) (string, error) { if err := s3utils.CheckValidBucketName(bucketName); err != nil { return "", err } // Region set then no need to fetch bucket location. if c.region != "" { return c.region, nil } if location, ok := c.bucketLocCache.Get(bucketName); ok { return location, nil } // Initialize a new request. req, err := c.getBucketLocationRequest(bucketName) if err != nil { return "", err } // Initiate the request. resp, err := c.do(req) defer closeResponse(resp) if err != nil { return "", err } location, err := processBucketLocationResponse(resp, bucketName) if err != nil { return "", err } c.bucketLocCache.Set(bucketName, location) return location, nil } // processes the getBucketLocation http response from the server. func processBucketLocationResponse(resp *http.Response, bucketName string) (bucketLocation string, err error) { if resp != nil { if resp.StatusCode != http.StatusOK { err = httpRespToErrorResponse(resp, bucketName, "") errResp := ToErrorResponse(err) // For access denied error, it could be an anonymous // request. Move forward and let the top level callers // succeed if possible based on their policy. if errResp.Code == "AccessDenied" { return "us-east-1", nil } return "", err } } // Extract location. var locationConstraint string err = xmlDecoder(resp.Body, &locationConstraint) if err != nil { return "", err } location := locationConstraint // Location is empty will be 'us-east-1'. if location == "" { location = "us-east-1" } // Location can be 'EU' convert it to meaningful 'eu-west-1'. if location == "EU" { location = "eu-west-1" } // Save the location into cache. // Return. return location, nil } // getBucketLocationRequest - Wrapper creates a new getBucketLocation request. func (c Client) getBucketLocationRequest(bucketName string) (*http.Request, error) { // Set location query. urlValues := make(url.Values) urlValues.Set("location", "") // Set get bucket location always as path style. targetURL := c.endpointURL targetURL.Path = path.Join(bucketName, "") + "/" targetURL.RawQuery = urlValues.Encode() // Get a new HTTP request for the method. req, err := http.NewRequest("GET", targetURL.String(), nil) if err != nil { return nil, err } // Set UserAgent for the request. c.setUserAgent(req) // Get credentials from the configured credentials provider. value, err := c.credsProvider.Get() if err != nil { return nil, err } var ( signerType = value.SignerType accessKeyID = value.AccessKeyID secretAccessKey = value.SecretAccessKey sessionToken = value.SessionToken ) // Custom signer set then override the behavior. if c.overrideSignerType != credentials.SignatureDefault { signerType = c.overrideSignerType } // If signerType returned by credentials helper is anonymous, // then do not sign regardless of signerType override. if value.SignerType == credentials.SignatureAnonymous { signerType = credentials.SignatureAnonymous } if signerType.IsAnonymous() { return req, nil } if signerType.IsV2() { // Get Bucket Location calls should be always path style isVirtualHost := false req = s3signer.SignV2(*req, accessKeyID, secretAccessKey, isVirtualHost) return req, nil } // Set sha256 sum for signature calculation only with signature version '4'. contentSha256 := emptySHA256Hex if c.secure { contentSha256 = unsignedPayload } req.Header.Set("X-Amz-Content-Sha256", contentSha256) req = s3signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, "us-east-1") return req, nil }