github.com/huaweicloud/golangsdk@v0.0.0-20210831081626-d823fe11ceba/openstack/obs/conf.go (about)

     1  // Copyright 2019 Huawei Technologies Co.,Ltd.
     2  // Licensed under the Apache License, Version 2.0 (the "License"); you may not use
     3  // this file except in compliance with the License.  You may obtain a copy of the
     4  // License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software distributed
     9  // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    10  // CONDITIONS OF ANY KIND, either express or implied.  See the License for the
    11  // specific language governing permissions and limitations under the License.
    12  
    13  package obs
    14  
    15  import (
    16  	"context"
    17  	"crypto/tls"
    18  	"crypto/x509"
    19  	"errors"
    20  	"fmt"
    21  	"net"
    22  	"net/http"
    23  	"net/url"
    24  	"sort"
    25  	"strconv"
    26  	"strings"
    27  	"time"
    28  )
    29  
    30  type urlHolder struct {
    31  	scheme string
    32  	host   string
    33  	port   int
    34  }
    35  
    36  type config struct {
    37  	securityProviders []securityProvider
    38  	urlHolder         *urlHolder
    39  	pathStyle         bool
    40  	cname             bool
    41  	sslVerify         bool
    42  	endpoint          string
    43  	signature         SignatureType
    44  	region            string
    45  	connectTimeout    int
    46  	socketTimeout     int
    47  	headerTimeout     int
    48  	idleConnTimeout   int
    49  	finalTimeout      int
    50  	maxRetryCount     int
    51  	proxyURL          string
    52  	maxConnsPerHost   int
    53  	pemCerts          []byte
    54  	transport         *http.Transport
    55  	ctx               context.Context
    56  	maxRedirectCount  int
    57  	userAgent         string
    58  	enableCompression bool
    59  }
    60  
    61  func (conf config) String() string {
    62  	return fmt.Sprintf("[endpoint:%s, signature:%s, pathStyle:%v, region:%s"+
    63  		"\nconnectTimeout:%d, socketTimeout:%dheaderTimeout:%d, idleConnTimeout:%d"+
    64  		"\nmaxRetryCount:%d, maxConnsPerHost:%d, sslVerify:%v, maxRedirectCount:%d]",
    65  		conf.endpoint, conf.signature, conf.pathStyle, conf.region,
    66  		conf.connectTimeout, conf.socketTimeout, conf.headerTimeout, conf.idleConnTimeout,
    67  		conf.maxRetryCount, conf.maxConnsPerHost, conf.sslVerify, conf.maxRedirectCount,
    68  	)
    69  }
    70  
    71  type configurer func(conf *config)
    72  
    73  func WithSecurityProviders(sps ...securityProvider) configurer {
    74  	return func(conf *config) {
    75  		for _, sp := range sps {
    76  			if sp != nil {
    77  				conf.securityProviders = append(conf.securityProviders, sp)
    78  			}
    79  		}
    80  	}
    81  }
    82  
    83  // WithSslVerify is a wrapper for WithSslVerifyAndPemCerts.
    84  func WithSslVerify(sslVerify bool) configurer {
    85  	return WithSslVerifyAndPemCerts(sslVerify, nil)
    86  }
    87  
    88  // WithSslVerifyAndPemCerts is a configurer for ObsClient to set conf.sslVerify and conf.pemCerts.
    89  func WithSslVerifyAndPemCerts(sslVerify bool, pemCerts []byte) configurer {
    90  	return func(conf *config) {
    91  		conf.sslVerify = sslVerify
    92  		conf.pemCerts = pemCerts
    93  	}
    94  }
    95  
    96  // WithHeaderTimeout is a configurer for ObsClient to set the timeout period of obtaining the response headers.
    97  func WithHeaderTimeout(headerTimeout int) configurer {
    98  	return func(conf *config) {
    99  		conf.headerTimeout = headerTimeout
   100  	}
   101  }
   102  
   103  // WithProxyUrl is a configurer for ObsClient to set HTTP proxy.
   104  func WithProxyUrl(proxyURL string) configurer {
   105  	return func(conf *config) {
   106  		conf.proxyURL = proxyURL
   107  	}
   108  }
   109  
   110  // WithMaxConnections is a configurer for ObsClient to set the maximum number of idle HTTP connections.
   111  func WithMaxConnections(maxConnsPerHost int) configurer {
   112  	return func(conf *config) {
   113  		conf.maxConnsPerHost = maxConnsPerHost
   114  	}
   115  }
   116  
   117  // WithPathStyle is a configurer for ObsClient.
   118  func WithPathStyle(pathStyle bool) configurer {
   119  	return func(conf *config) {
   120  		conf.pathStyle = pathStyle
   121  	}
   122  }
   123  
   124  // WithSignature is a configurer for ObsClient.
   125  func WithSignature(signature SignatureType) configurer {
   126  	return func(conf *config) {
   127  		conf.signature = signature
   128  	}
   129  }
   130  
   131  // WithRegion is a configurer for ObsClient.
   132  func WithRegion(region string) configurer {
   133  	return func(conf *config) {
   134  		conf.region = region
   135  	}
   136  }
   137  
   138  // WithConnectTimeout is a configurer for ObsClient to set timeout period for establishing
   139  // an http/https connection, in seconds.
   140  func WithConnectTimeout(connectTimeout int) configurer {
   141  	return func(conf *config) {
   142  		conf.connectTimeout = connectTimeout
   143  	}
   144  }
   145  
   146  // WithSocketTimeout is a configurer for ObsClient to set the timeout duration for transmitting data at
   147  // the socket layer, in seconds.
   148  func WithSocketTimeout(socketTimeout int) configurer {
   149  	return func(conf *config) {
   150  		conf.socketTimeout = socketTimeout
   151  	}
   152  }
   153  
   154  // WithIdleConnTimeout is a configurer for ObsClient to set the timeout period of an idle HTTP connection
   155  // in the connection pool, in seconds.
   156  func WithIdleConnTimeout(idleConnTimeout int) configurer {
   157  	return func(conf *config) {
   158  		conf.idleConnTimeout = idleConnTimeout
   159  	}
   160  }
   161  
   162  // WithMaxRetryCount is a configurer for ObsClient to set the maximum number of retries when an HTTP/HTTPS connection is abnormal.
   163  func WithMaxRetryCount(maxRetryCount int) configurer {
   164  	return func(conf *config) {
   165  		conf.maxRetryCount = maxRetryCount
   166  	}
   167  }
   168  
   169  // WithSecurityToken is a configurer for ObsClient to set the security token in the temporary access keys.
   170  func WithSecurityToken(securityToken string) configurer {
   171  	return func(conf *config) {
   172  		for _, sp := range conf.securityProviders {
   173  			if bsp, ok := sp.(*BasicSecurityProvider); ok {
   174  				sh := bsp.getSecurity()
   175  				bsp.refresh(sh.ak, sh.sk, securityToken)
   176  				break
   177  			}
   178  		}
   179  	}
   180  }
   181  
   182  // WithHttpTransport is a configurer for ObsClient to set the customized http Transport.
   183  func WithHttpTransport(transport *http.Transport) configurer {
   184  	return func(conf *config) {
   185  		conf.transport = transport
   186  	}
   187  }
   188  
   189  // WithRequestContext is a configurer for ObsClient to set the context for each HTTP request.
   190  func WithRequestContext(ctx context.Context) configurer {
   191  	return func(conf *config) {
   192  		conf.ctx = ctx
   193  	}
   194  }
   195  
   196  // WithCustomDomainName is a configurer for ObsClient.
   197  func WithCustomDomainName(cname bool) configurer {
   198  	return func(conf *config) {
   199  		conf.cname = cname
   200  	}
   201  }
   202  
   203  // WithMaxRedirectCount is a configurer for ObsClient to set the maximum number of times that the request is redirected.
   204  func WithMaxRedirectCount(maxRedirectCount int) configurer {
   205  	return func(conf *config) {
   206  		conf.maxRedirectCount = maxRedirectCount
   207  	}
   208  }
   209  
   210  // WithUserAgent is a configurer for ObsClient to set the User-Agent.
   211  func WithUserAgent(userAgent string) configurer {
   212  	return func(conf *config) {
   213  		conf.userAgent = userAgent
   214  	}
   215  }
   216  
   217  // WithEnableCompression is a configurer for ObsClient to set the Transport.DisableCompression.
   218  func WithEnableCompression(enableCompression bool) configurer {
   219  	return func(conf *config) {
   220  		conf.enableCompression = enableCompression
   221  	}
   222  }
   223  
   224  func (conf *config) prepareConfig() {
   225  	if conf.connectTimeout <= 0 {
   226  		conf.connectTimeout = DEFAULT_CONNECT_TIMEOUT
   227  	}
   228  
   229  	if conf.socketTimeout <= 0 {
   230  		conf.socketTimeout = DEFAULT_SOCKET_TIMEOUT
   231  	}
   232  
   233  	conf.finalTimeout = conf.socketTimeout * 10
   234  
   235  	if conf.headerTimeout <= 0 {
   236  		conf.headerTimeout = DEFAULT_HEADER_TIMEOUT
   237  	}
   238  
   239  	if conf.idleConnTimeout < 0 {
   240  		conf.idleConnTimeout = DEFAULT_IDLE_CONN_TIMEOUT
   241  	}
   242  
   243  	if conf.maxRetryCount < 0 {
   244  		conf.maxRetryCount = DEFAULT_MAX_RETRY_COUNT
   245  	}
   246  
   247  	if conf.maxConnsPerHost <= 0 {
   248  		conf.maxConnsPerHost = DEFAULT_MAX_CONN_PER_HOST
   249  	}
   250  
   251  	if conf.maxRedirectCount < 0 {
   252  		conf.maxRedirectCount = DEFAULT_MAX_REDIRECT_COUNT
   253  	}
   254  
   255  	if conf.pathStyle && conf.signature == SignatureObs {
   256  		conf.signature = SignatureV2
   257  	}
   258  }
   259  
   260  func (conf *config) initConfigWithDefault() error {
   261  	conf.endpoint = strings.TrimSpace(conf.endpoint)
   262  	if conf.endpoint == "" {
   263  		return errors.New("endpoint is not set")
   264  	}
   265  
   266  	if index := strings.Index(conf.endpoint, "?"); index > 0 {
   267  		conf.endpoint = conf.endpoint[:index]
   268  	}
   269  
   270  	for strings.LastIndex(conf.endpoint, "/") == len(conf.endpoint)-1 {
   271  		conf.endpoint = conf.endpoint[:len(conf.endpoint)-1]
   272  	}
   273  
   274  	if conf.signature == "" {
   275  		conf.signature = DEFAULT_SIGNATURE
   276  	}
   277  
   278  	urlHolder := &urlHolder{}
   279  	var address string
   280  	if strings.HasPrefix(conf.endpoint, "https://") {
   281  		urlHolder.scheme = "https"
   282  		address = conf.endpoint[len("https://"):]
   283  	} else if strings.HasPrefix(conf.endpoint, "http://") {
   284  		urlHolder.scheme = "http"
   285  		address = conf.endpoint[len("http://"):]
   286  	} else {
   287  		urlHolder.scheme = "https"
   288  		address = conf.endpoint
   289  	}
   290  
   291  	addr := strings.Split(address, ":")
   292  	if len(addr) == 2 {
   293  		if port, err := strconv.Atoi(addr[1]); err == nil {
   294  			urlHolder.port = port
   295  		}
   296  	}
   297  	urlHolder.host = addr[0]
   298  	if urlHolder.port == 0 {
   299  		if urlHolder.scheme == "https" {
   300  			urlHolder.port = 443
   301  		} else {
   302  			urlHolder.port = 80
   303  		}
   304  	}
   305  
   306  	if IsIP(urlHolder.host) {
   307  		conf.pathStyle = true
   308  	}
   309  
   310  	conf.urlHolder = urlHolder
   311  
   312  	conf.region = strings.TrimSpace(conf.region)
   313  	if conf.region == "" {
   314  		conf.region = DEFAULT_REGION
   315  	}
   316  
   317  	conf.prepareConfig()
   318  	conf.proxyURL = strings.TrimSpace(conf.proxyURL)
   319  	return nil
   320  }
   321  
   322  func (conf *config) getTransport() error {
   323  	if conf.transport == nil {
   324  		conf.transport = &http.Transport{
   325  			Dial: func(network, addr string) (net.Conn, error) {
   326  				conn, err := net.DialTimeout(network, addr, time.Second*time.Duration(conf.connectTimeout))
   327  				if err != nil {
   328  					return nil, err
   329  				}
   330  				return getConnDelegate(conn, conf.socketTimeout, conf.finalTimeout), nil
   331  			},
   332  			MaxIdleConns:          conf.maxConnsPerHost,
   333  			MaxIdleConnsPerHost:   conf.maxConnsPerHost,
   334  			ResponseHeaderTimeout: time.Second * time.Duration(conf.headerTimeout),
   335  			IdleConnTimeout:       time.Second * time.Duration(conf.idleConnTimeout),
   336  		}
   337  
   338  		if conf.proxyURL != "" {
   339  			proxyURL, err := url.Parse(conf.proxyURL)
   340  			if err != nil {
   341  				return err
   342  			}
   343  			conf.transport.Proxy = http.ProxyURL(proxyURL)
   344  		}
   345  
   346  		tlsConfig := &tls.Config{InsecureSkipVerify: !conf.sslVerify}
   347  		if conf.sslVerify && conf.pemCerts != nil {
   348  			pool := x509.NewCertPool()
   349  			pool.AppendCertsFromPEM(conf.pemCerts)
   350  			tlsConfig.RootCAs = pool
   351  		}
   352  
   353  		conf.transport.TLSClientConfig = tlsConfig
   354  		conf.transport.DisableCompression = !conf.enableCompression
   355  	}
   356  
   357  	return nil
   358  }
   359  
   360  func checkRedirectFunc(req *http.Request, via []*http.Request) error {
   361  	return http.ErrUseLastResponse
   362  }
   363  
   364  // DummyQueryEscape return the input string.
   365  func DummyQueryEscape(s string) string {
   366  	return s
   367  }
   368  
   369  func (conf *config) prepareBaseURL(bucketName string) (requestURL string, canonicalizedURL string) {
   370  	urlHolder := conf.urlHolder
   371  	if conf.cname {
   372  		requestURL = fmt.Sprintf("%s://%s:%d", urlHolder.scheme, urlHolder.host, urlHolder.port)
   373  		if conf.signature == "v4" {
   374  			canonicalizedURL = "/"
   375  		} else {
   376  			canonicalizedURL = "/" + urlHolder.host + "/"
   377  		}
   378  	} else {
   379  		if bucketName == "" {
   380  			requestURL = fmt.Sprintf("%s://%s:%d", urlHolder.scheme, urlHolder.host, urlHolder.port)
   381  			canonicalizedURL = "/"
   382  		} else {
   383  			if conf.pathStyle {
   384  				requestURL = fmt.Sprintf("%s://%s:%d/%s", urlHolder.scheme, urlHolder.host, urlHolder.port, bucketName)
   385  				canonicalizedURL = "/" + bucketName
   386  			} else {
   387  				requestURL = fmt.Sprintf("%s://%s.%s:%d", urlHolder.scheme, bucketName, urlHolder.host, urlHolder.port)
   388  				if conf.signature == "v2" || conf.signature == "OBS" {
   389  					canonicalizedURL = "/" + bucketName + "/"
   390  				} else {
   391  					canonicalizedURL = "/"
   392  				}
   393  			}
   394  		}
   395  	}
   396  	return
   397  }
   398  
   399  func (conf *config) prepareObjectKey(escape bool, objectKey string, escapeFunc func(s string) string) (encodeObjectKey string) {
   400  	if escape {
   401  		tempKey := []rune(objectKey)
   402  		result := make([]string, 0, len(tempKey))
   403  		for _, value := range tempKey {
   404  			if string(value) == "/" {
   405  				result = append(result, string(value))
   406  			} else {
   407  				if string(value) == " " {
   408  					result = append(result, url.PathEscape(string(value)))
   409  				} else {
   410  					result = append(result, url.QueryEscape(string(value)))
   411  				}
   412  			}
   413  		}
   414  		encodeObjectKey = strings.Join(result, "")
   415  	} else {
   416  		encodeObjectKey = escapeFunc(objectKey)
   417  	}
   418  	return
   419  }
   420  
   421  func (conf *config) prepareEscapeFunc(escape bool) (escapeFunc func(s string) string) {
   422  	if escape {
   423  		return url.QueryEscape
   424  	}
   425  	return DummyQueryEscape
   426  }
   427  
   428  func (conf *config) formatUrls(bucketName, objectKey string, params map[string]string, escape bool) (requestURL string, canonicalizedURL string) {
   429  
   430  	requestURL, canonicalizedURL = conf.prepareBaseURL(bucketName)
   431  	var escapeFunc func(s string) string
   432  	escapeFunc = conf.prepareEscapeFunc(escape)
   433  
   434  	if objectKey != "" {
   435  		var encodeObjectKey string
   436  		encodeObjectKey = conf.prepareObjectKey(escape, objectKey, escapeFunc)
   437  		requestURL += "/" + encodeObjectKey
   438  		if !strings.HasSuffix(canonicalizedURL, "/") {
   439  			canonicalizedURL += "/"
   440  		}
   441  		canonicalizedURL += encodeObjectKey
   442  	}
   443  
   444  	keys := make([]string, 0, len(params))
   445  	for key := range params {
   446  		keys = append(keys, strings.TrimSpace(key))
   447  	}
   448  	sort.Strings(keys)
   449  	i := 0
   450  
   451  	for index, key := range keys {
   452  		if index == 0 {
   453  			requestURL += "?"
   454  		} else {
   455  			requestURL += "&"
   456  		}
   457  		_key := url.QueryEscape(key)
   458  		requestURL += _key
   459  
   460  		_value := params[key]
   461  		if conf.signature == "v4" {
   462  			requestURL += "=" + url.QueryEscape(_value)
   463  		} else {
   464  			if _value != "" {
   465  				requestURL += "=" + url.QueryEscape(_value)
   466  				_value = "=" + _value
   467  			} else {
   468  				_value = ""
   469  			}
   470  			lowerKey := strings.ToLower(key)
   471  			_, ok := allowedResourceParameterNames[lowerKey]
   472  			prefixHeader := HEADER_PREFIX
   473  			isObs := conf.signature == SignatureObs
   474  			if isObs {
   475  				prefixHeader = HEADER_PREFIX_OBS
   476  			}
   477  			ok = ok || strings.HasPrefix(lowerKey, prefixHeader)
   478  			if ok {
   479  				if i == 0 {
   480  					canonicalizedURL += "?"
   481  				} else {
   482  					canonicalizedURL += "&"
   483  				}
   484  				canonicalizedURL += getQueryURL(_key, _value)
   485  				i++
   486  			}
   487  		}
   488  	}
   489  	return
   490  }
   491  
   492  func getQueryURL(key, value string) string {
   493  	queryURL := ""
   494  	queryURL += key
   495  	queryURL += value
   496  	return queryURL
   497  }