gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

bucket-cache.go (7032B)


      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 
     18 package minio
     19 
     20 import (
     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.
     35 type 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.
     46 func 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.
     53 func (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.
     61 func (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.
     68 func (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.
     76 func (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.
     85 func (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.
    120 func 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 				switch errResp.Server {
    131 				case "AmazonSnowball":
    132 					return "snowball", nil
    133 				case "cloudflare":
    134 					return "us-east-1", nil
    135 				}
    136 			case "AuthorizationHeaderMalformed":
    137 				fallthrough
    138 			case "InvalidRegion":
    139 				fallthrough
    140 			case "AccessDenied":
    141 				if errResp.Region == "" {
    142 					return "us-east-1", nil
    143 				}
    144 				return errResp.Region, nil
    145 			}
    146 			return "", err
    147 		}
    148 	}
    149 
    150 	// Extract location.
    151 	var locationConstraint string
    152 	err = xmlDecoder(resp.Body, &locationConstraint)
    153 	if err != nil {
    154 		return "", err
    155 	}
    156 
    157 	location := locationConstraint
    158 	// Location is empty will be 'us-east-1'.
    159 	if location == "" {
    160 		location = "us-east-1"
    161 	}
    162 
    163 	// Location can be 'EU' convert it to meaningful 'eu-west-1'.
    164 	if location == "EU" {
    165 		location = "eu-west-1"
    166 	}
    167 
    168 	// Save the location into cache.
    169 
    170 	// Return.
    171 	return location, nil
    172 }
    173 
    174 // getBucketLocationRequest - Wrapper creates a new getBucketLocation request.
    175 func (c *Client) getBucketLocationRequest(ctx context.Context, bucketName string) (*http.Request, error) {
    176 	// Set location query.
    177 	urlValues := make(url.Values)
    178 	urlValues.Set("location", "")
    179 
    180 	// Set get bucket location always as path style.
    181 	targetURL := *c.endpointURL
    182 
    183 	// as it works in makeTargetURL method from api.go file
    184 	if h, p, err := net.SplitHostPort(targetURL.Host); err == nil {
    185 		if targetURL.Scheme == "http" && p == "80" || targetURL.Scheme == "https" && p == "443" {
    186 			targetURL.Host = h
    187 			if ip := net.ParseIP(h); ip != nil && ip.To16() != nil {
    188 				targetURL.Host = "[" + h + "]"
    189 			}
    190 		}
    191 	}
    192 
    193 	isVirtualStyle := c.isVirtualHostStyleRequest(targetURL, bucketName)
    194 
    195 	var urlStr string
    196 
    197 	if isVirtualStyle {
    198 		urlStr = c.endpointURL.Scheme + "://" + bucketName + "." + targetURL.Host + "/?location"
    199 	} else {
    200 		targetURL.Path = path.Join(bucketName, "") + "/"
    201 		targetURL.RawQuery = urlValues.Encode()
    202 		urlStr = targetURL.String()
    203 	}
    204 
    205 	// Get a new HTTP request for the method.
    206 	req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil)
    207 	if err != nil {
    208 		return nil, err
    209 	}
    210 
    211 	// Set UserAgent for the request.
    212 	c.setUserAgent(req)
    213 
    214 	// Get credentials from the configured credentials provider.
    215 	value, err := c.credsProvider.Get()
    216 	if err != nil {
    217 		return nil, err
    218 	}
    219 
    220 	var (
    221 		signerType      = value.SignerType
    222 		accessKeyID     = value.AccessKeyID
    223 		secretAccessKey = value.SecretAccessKey
    224 		sessionToken    = value.SessionToken
    225 	)
    226 
    227 	// Custom signer set then override the behavior.
    228 	if c.overrideSignerType != credentials.SignatureDefault {
    229 		signerType = c.overrideSignerType
    230 	}
    231 
    232 	// If signerType returned by credentials helper is anonymous,
    233 	// then do not sign regardless of signerType override.
    234 	if value.SignerType == credentials.SignatureAnonymous {
    235 		signerType = credentials.SignatureAnonymous
    236 	}
    237 
    238 	if signerType.IsAnonymous() {
    239 		return req, nil
    240 	}
    241 
    242 	if signerType.IsV2() {
    243 		req = signer.SignV2(*req, accessKeyID, secretAccessKey, isVirtualStyle)
    244 		return req, nil
    245 	}
    246 
    247 	// Set sha256 sum for signature calculation only with signature version '4'.
    248 	contentSha256 := emptySHA256Hex
    249 	if c.secure {
    250 		contentSha256 = unsignedPayload
    251 	}
    252 
    253 	req.Header.Set("X-Amz-Content-Sha256", contentSha256)
    254 	req = signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, "us-east-1")
    255 	return req, nil
    256 }