Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
253 lines
6.9 KiB
253 lines
6.9 KiB
/* |
|
* 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 ( |
|
"context" |
|
"net" |
|
"net/http" |
|
"net/url" |
|
"path" |
|
"sync" |
|
|
|
"github.com/minio/minio-go/v7/pkg/credentials" |
|
"github.com/minio/minio-go/v7/pkg/s3utils" |
|
"github.com/minio/minio-go/v7/pkg/signer" |
|
) |
|
|
|
// 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(ctx context.Context, bucketName string) (string, error) { |
|
if err := s3utils.CheckValidBucketName(bucketName); err != nil { |
|
return "", err |
|
} |
|
return c.getBucketLocation(ctx, bucketName) |
|
} |
|
|
|
// getBucketLocation - Get location for the bucketName from location map cache, if not |
|
// fetch freshly by making a new request. |
|
func (c Client) getBucketLocation(ctx context.Context, 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. |
|
switch errResp.Code { |
|
case "NotImplemented": |
|
if errResp.Server == "AmazonSnowball" { |
|
return "snowball", nil |
|
} |
|
case "AuthorizationHeaderMalformed": |
|
fallthrough |
|
case "InvalidRegion": |
|
fallthrough |
|
case "AccessDenied": |
|
if errResp.Region == "" { |
|
return "us-east-1", nil |
|
} |
|
return errResp.Region, 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 |
|
|
|
// as it works in makeTargetURL method from api.go file |
|
if h, p, err := net.SplitHostPort(targetURL.Host); err == nil { |
|
if targetURL.Scheme == "http" && p == "80" || targetURL.Scheme == "https" && p == "443" { |
|
targetURL.Host = h |
|
} |
|
} |
|
|
|
isVirtualHost := s3utils.IsVirtualHostSupported(targetURL, bucketName) |
|
|
|
var urlStr string |
|
|
|
//only support Aliyun OSS for virtual hosted path, compatible Amazon & Google Endpoint |
|
if isVirtualHost && s3utils.IsAliyunOSSEndpoint(targetURL) { |
|
urlStr = c.endpointURL.Scheme + "://" + bucketName + "." + targetURL.Host + "/?location" |
|
} else { |
|
targetURL.Path = path.Join(bucketName, "") + "/" |
|
targetURL.RawQuery = urlValues.Encode() |
|
urlStr = targetURL.String() |
|
} |
|
|
|
// Get a new HTTP request for the method. |
|
req, err := http.NewRequest(http.MethodGet, urlStr, 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 = signer.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 = signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, "us-east-1") |
|
return req, nil |
|
}
|
|
|