github.com/chnsz/golangsdk@v0.0.0-20240506093406-85a3fbfa605b/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  	"os"
    25  	"sort"
    26  	"strconv"
    27  	"strings"
    28  	"sync"
    29  	"time"
    30  
    31  	"golang.org/x/net/http/httpproxy"
    32  )
    33  
    34  type urlHolder struct {
    35  	scheme string
    36  	host   string
    37  	port   int
    38  }
    39  
    40  type config struct {
    41  	securityProviders    []securityProvider
    42  	urlHolder            *urlHolder
    43  	pathStyle            bool
    44  	cname                bool
    45  	sslVerify            bool
    46  	disableKeepAlive     bool
    47  	endpoint             string
    48  	signature            SignatureType
    49  	region               string
    50  	connectTimeout       int
    51  	socketTimeout        int
    52  	headerTimeout        int
    53  	idleConnTimeout      int
    54  	finalTimeout         int
    55  	maxRetryCount        int
    56  	proxyURL             string
    57  	noProxyURL           string
    58  	proxyFromEnv         bool
    59  	maxConnsPerHost      int
    60  	pemCerts             []byte
    61  	transport            *http.Transport
    62  	roundTripper         http.RoundTripper
    63  	httpClient           *http.Client
    64  	ctx                  context.Context
    65  	maxRedirectCount     int
    66  	userAgent            string
    67  	enableCompression    bool
    68  	progressListener     ProgressListener
    69  	customProxyOnce      sync.Once
    70  	customProxyFuncValue func(*url.URL) (*url.URL, error)
    71  }
    72  
    73  func (conf *config) String() string {
    74  	return fmt.Sprintf("[endpoint:%s, signature:%s, pathStyle:%v, region:%s"+
    75  		"\nconnectTimeout:%d, socketTimeout:%dheaderTimeout:%d, idleConnTimeout:%d"+
    76  		"\nmaxRetryCount:%d, maxConnsPerHost:%d, sslVerify:%v, maxRedirectCount:%d]",
    77  		conf.endpoint, conf.signature, conf.pathStyle, conf.region,
    78  		conf.connectTimeout, conf.socketTimeout, conf.headerTimeout, conf.idleConnTimeout,
    79  		conf.maxRetryCount, conf.maxConnsPerHost, conf.sslVerify, conf.maxRedirectCount,
    80  	)
    81  }
    82  
    83  type configurer func(conf *config)
    84  
    85  func WithSecurityProviders(sps ...securityProvider) configurer {
    86  	return func(conf *config) {
    87  		for _, sp := range sps {
    88  			if sp != nil {
    89  				conf.securityProviders = append(conf.securityProviders, sp)
    90  			}
    91  		}
    92  	}
    93  }
    94  
    95  // WithSslVerify is a wrapper for WithSslVerifyAndPemCerts.
    96  func WithSslVerify(sslVerify bool) configurer {
    97  	return WithSslVerifyAndPemCerts(sslVerify, nil)
    98  }
    99  
   100  // WithSslVerifyAndPemCerts is a configurer for ObsClient to set conf.sslVerify and conf.pemCerts.
   101  func WithSslVerifyAndPemCerts(sslVerify bool, pemCerts []byte) configurer {
   102  	return func(conf *config) {
   103  		conf.sslVerify = sslVerify
   104  		conf.pemCerts = pemCerts
   105  	}
   106  }
   107  
   108  // WithHeaderTimeout is a configurer for ObsClient to set the timeout period of obtaining the response headers.
   109  func WithHeaderTimeout(headerTimeout int) configurer {
   110  	return func(conf *config) {
   111  		conf.headerTimeout = headerTimeout
   112  	}
   113  }
   114  
   115  // WithProxyUrl is a configurer for ObsClient to set HTTP proxy.
   116  func WithProxyUrl(proxyURL string) configurer {
   117  	return func(conf *config) {
   118  		conf.proxyURL = proxyURL
   119  	}
   120  }
   121  
   122  // WithNoProxyUrl is a configurer for ObsClient to set HTTP no_proxy.
   123  func WithNoProxyUrl(noProxyURL string) configurer {
   124  	return func(conf *config) {
   125  		conf.noProxyURL = noProxyURL
   126  	}
   127  }
   128  
   129  // WithProxyFromEnv is a configurer for ObsClient to get proxy from evironment.
   130  func WithProxyFromEnv(proxyFromEnv bool) configurer {
   131  	return func(conf *config) {
   132  		conf.proxyFromEnv = proxyFromEnv
   133  	}
   134  }
   135  
   136  // WithMaxConnections is a configurer for ObsClient to set the maximum number of idle HTTP connections.
   137  func WithMaxConnections(maxConnsPerHost int) configurer {
   138  	return func(conf *config) {
   139  		conf.maxConnsPerHost = maxConnsPerHost
   140  	}
   141  }
   142  
   143  // WithPathStyle is a configurer for ObsClient.
   144  func WithPathStyle(pathStyle bool) configurer {
   145  	return func(conf *config) {
   146  		conf.pathStyle = pathStyle
   147  	}
   148  }
   149  
   150  // WithSignature is a configurer for ObsClient.
   151  func WithSignature(signature SignatureType) configurer {
   152  	return func(conf *config) {
   153  		conf.signature = signature
   154  	}
   155  }
   156  
   157  // WithRegion is a configurer for ObsClient.
   158  func WithRegion(region string) configurer {
   159  	return func(conf *config) {
   160  		conf.region = region
   161  	}
   162  }
   163  
   164  // WithConnectTimeout is a configurer for ObsClient to set timeout period for establishing
   165  // an http/https connection, in seconds.
   166  func WithConnectTimeout(connectTimeout int) configurer {
   167  	return func(conf *config) {
   168  		conf.connectTimeout = connectTimeout
   169  	}
   170  }
   171  
   172  // WithSocketTimeout is a configurer for ObsClient to set the timeout duration for transmitting data at
   173  // the socket layer, in seconds.
   174  func WithSocketTimeout(socketTimeout int) configurer {
   175  	return func(conf *config) {
   176  		conf.socketTimeout = socketTimeout
   177  	}
   178  }
   179  
   180  // WithIdleConnTimeout is a configurer for ObsClient to set the timeout period of an idle HTTP connection
   181  // in the connection pool, in seconds.
   182  func WithIdleConnTimeout(idleConnTimeout int) configurer {
   183  	return func(conf *config) {
   184  		conf.idleConnTimeout = idleConnTimeout
   185  	}
   186  }
   187  
   188  // WithMaxRetryCount is a configurer for ObsClient to set the maximum number of retries when an HTTP/HTTPS connection is abnormal.
   189  func WithMaxRetryCount(maxRetryCount int) configurer {
   190  	return func(conf *config) {
   191  		conf.maxRetryCount = maxRetryCount
   192  	}
   193  }
   194  
   195  // WithSecurityToken is a configurer for ObsClient to set the security token in the temporary access keys.
   196  func WithSecurityToken(securityToken string) configurer {
   197  	return func(conf *config) {
   198  		for _, sp := range conf.securityProviders {
   199  			if bsp, ok := sp.(*BasicSecurityProvider); ok {
   200  				sh := bsp.getSecurity()
   201  				bsp.refresh(sh.ak, sh.sk, securityToken)
   202  				break
   203  			}
   204  		}
   205  	}
   206  }
   207  
   208  // WithHttpTransport is a configurer for ObsClient to set the customized http Transport.
   209  func WithHttpTransport(transport *http.Transport) configurer {
   210  	return func(conf *config) {
   211  		conf.transport = transport
   212  	}
   213  }
   214  
   215  func WithHttpClient(httpClient *http.Client) configurer {
   216  	return func(conf *config) {
   217  		conf.httpClient = httpClient
   218  	}
   219  }
   220  
   221  // WithRequestContext is a configurer for ObsClient to set the context for each HTTP request.
   222  func WithRequestContext(ctx context.Context) configurer {
   223  	return func(conf *config) {
   224  		conf.ctx = ctx
   225  	}
   226  }
   227  
   228  // WithCustomDomainName is a configurer for ObsClient.
   229  func WithCustomDomainName(cname bool) configurer {
   230  	return func(conf *config) {
   231  		conf.cname = cname
   232  	}
   233  }
   234  
   235  // WithDisableKeepAlive is a configurer for ObsClient to disable the keep-alive for http.
   236  func WithDisableKeepAlive(disableKeepAlive bool) configurer {
   237  	return func(conf *config) {
   238  		conf.disableKeepAlive = disableKeepAlive
   239  	}
   240  }
   241  
   242  // WithMaxRedirectCount is a configurer for ObsClient to set the maximum number of times that the request is redirected.
   243  func WithMaxRedirectCount(maxRedirectCount int) configurer {
   244  	return func(conf *config) {
   245  		conf.maxRedirectCount = maxRedirectCount
   246  	}
   247  }
   248  
   249  // WithUserAgent is a configurer for ObsClient to set the User-Agent.
   250  func WithUserAgent(userAgent string) configurer {
   251  	return func(conf *config) {
   252  		conf.userAgent = userAgent
   253  	}
   254  }
   255  
   256  // WithEnableCompression is a configurer for ObsClient to set the Transport.DisableCompression.
   257  func WithEnableCompression(enableCompression bool) configurer {
   258  	return func(conf *config) {
   259  		conf.enableCompression = enableCompression
   260  	}
   261  }
   262  
   263  func (conf *config) prepareConfig() {
   264  	if conf.connectTimeout <= 0 {
   265  		conf.connectTimeout = DEFAULT_CONNECT_TIMEOUT
   266  	}
   267  
   268  	if conf.socketTimeout <= 0 {
   269  		conf.socketTimeout = DEFAULT_SOCKET_TIMEOUT
   270  	}
   271  
   272  	conf.finalTimeout = conf.socketTimeout * 10
   273  
   274  	if conf.headerTimeout <= 0 {
   275  		conf.headerTimeout = DEFAULT_HEADER_TIMEOUT
   276  	}
   277  
   278  	if conf.idleConnTimeout < 0 {
   279  		conf.idleConnTimeout = DEFAULT_IDLE_CONN_TIMEOUT
   280  	}
   281  
   282  	if conf.maxRetryCount < 0 {
   283  		conf.maxRetryCount = DEFAULT_MAX_RETRY_COUNT
   284  	}
   285  
   286  	if conf.maxConnsPerHost <= 0 {
   287  		conf.maxConnsPerHost = DEFAULT_MAX_CONN_PER_HOST
   288  	}
   289  
   290  	if conf.maxRedirectCount < 0 {
   291  		conf.maxRedirectCount = DEFAULT_MAX_REDIRECT_COUNT
   292  	}
   293  
   294  	if conf.pathStyle && conf.signature == SignatureObs {
   295  		conf.signature = SignatureV2
   296  	}
   297  }
   298  
   299  func (conf *config) initConfigWithDefault() error {
   300  	conf.endpoint = strings.TrimSpace(conf.endpoint)
   301  	if conf.endpoint == "" {
   302  		return errors.New("endpoint is not set")
   303  	}
   304  
   305  	if index := strings.Index(conf.endpoint, "?"); index > 0 {
   306  		conf.endpoint = conf.endpoint[:index]
   307  	}
   308  
   309  	for strings.LastIndex(conf.endpoint, "/") == len(conf.endpoint)-1 {
   310  		conf.endpoint = conf.endpoint[:len(conf.endpoint)-1]
   311  	}
   312  
   313  	if conf.signature == "" {
   314  		conf.signature = DEFAULT_SIGNATURE
   315  	}
   316  
   317  	urlHolder := &urlHolder{}
   318  	var address string
   319  	if strings.HasPrefix(conf.endpoint, "https://") {
   320  		urlHolder.scheme = "https"
   321  		address = conf.endpoint[len("https://"):]
   322  	} else if strings.HasPrefix(conf.endpoint, "http://") {
   323  		urlHolder.scheme = "http"
   324  		address = conf.endpoint[len("http://"):]
   325  	} else {
   326  		urlHolder.scheme = "https"
   327  		address = conf.endpoint
   328  	}
   329  
   330  	addr := strings.Split(address, ":")
   331  	if len(addr) == 2 {
   332  		if port, err := strconv.Atoi(addr[1]); err == nil {
   333  			urlHolder.port = port
   334  		}
   335  	}
   336  	urlHolder.host = addr[0]
   337  	if urlHolder.port == 0 {
   338  		if urlHolder.scheme == "https" {
   339  			urlHolder.port = 443
   340  		} else {
   341  			urlHolder.port = 80
   342  		}
   343  	}
   344  
   345  	if IsIP(urlHolder.host) {
   346  		conf.pathStyle = true
   347  	}
   348  
   349  	conf.urlHolder = urlHolder
   350  
   351  	conf.region = strings.TrimSpace(conf.region)
   352  	if conf.region == "" {
   353  		conf.region = DEFAULT_REGION
   354  	}
   355  
   356  	conf.prepareConfig()
   357  	conf.proxyURL = strings.TrimSpace(conf.proxyURL)
   358  	return nil
   359  }
   360  
   361  func (conf *config) getTransport() error {
   362  	if conf.transport == nil {
   363  		conf.transport = &http.Transport{
   364  			Dial: func(network, addr string) (net.Conn, error) {
   365  				conn, err := net.DialTimeout(network, addr, time.Second*time.Duration(conf.connectTimeout))
   366  				if err != nil {
   367  					return nil, err
   368  				}
   369  				return getConnDelegate(conn, conf.socketTimeout, conf.finalTimeout), nil
   370  			},
   371  			MaxIdleConns:          conf.maxConnsPerHost,
   372  			MaxIdleConnsPerHost:   conf.maxConnsPerHost,
   373  			ResponseHeaderTimeout: time.Second * time.Duration(conf.headerTimeout),
   374  			IdleConnTimeout:       time.Second * time.Duration(conf.idleConnTimeout),
   375  			DisableKeepAlives:     conf.disableKeepAlive,
   376  		}
   377  		if conf.proxyURL != "" {
   378  			conf.transport.Proxy = conf.customProxyFromEnvironment
   379  		} else if conf.proxyFromEnv {
   380  			conf.transport.Proxy = http.ProxyFromEnvironment
   381  		}
   382  
   383  		tlsConfig := &tls.Config{InsecureSkipVerify: !conf.sslVerify}
   384  		if conf.sslVerify && conf.pemCerts != nil {
   385  			pool := x509.NewCertPool()
   386  			pool.AppendCertsFromPEM(conf.pemCerts)
   387  			tlsConfig.RootCAs = pool
   388  		}
   389  
   390  		conf.transport.TLSClientConfig = tlsConfig
   391  		conf.transport.DisableCompression = !conf.enableCompression
   392  	}
   393  
   394  	return nil
   395  }
   396  
   397  func (conf *config) customProxyFromEnvironment(req *http.Request) (*url.URL, error) {
   398  	url, err := conf.customProxyFunc()(req.URL)
   399  	return url, err
   400  }
   401  
   402  func (conf *config) customProxyFunc() func(*url.URL) (*url.URL, error) {
   403  	conf.customProxyOnce.Do(func() {
   404  		customhttpproxy := &httpproxy.Config{
   405  			HTTPProxy:  conf.proxyURL,
   406  			HTTPSProxy: conf.proxyURL,
   407  			NoProxy:    conf.noProxyURL,
   408  			CGI:        os.Getenv("REQUEST_METHOD") != "",
   409  		}
   410  		conf.customProxyFuncValue = customhttpproxy.ProxyFunc()
   411  	})
   412  	return conf.customProxyFuncValue
   413  }
   414  
   415  func checkRedirectFunc(req *http.Request, via []*http.Request) error {
   416  	return http.ErrUseLastResponse
   417  }
   418  
   419  // DummyQueryEscape return the input string.
   420  func DummyQueryEscape(s string) string {
   421  	return s
   422  }
   423  
   424  func (conf *config) prepareBaseURL(bucketName string) (requestURL string, canonicalizedURL string) {
   425  	urlHolder := conf.urlHolder
   426  	if conf.cname {
   427  		requestURL = fmt.Sprintf("%s://%s:%d", urlHolder.scheme, urlHolder.host, urlHolder.port)
   428  		if conf.signature == "v4" {
   429  			canonicalizedURL = "/"
   430  		} else {
   431  			canonicalizedURL = "/" + urlHolder.host + "/"
   432  		}
   433  	} else {
   434  		if bucketName == "" {
   435  			requestURL = fmt.Sprintf("%s://%s:%d", urlHolder.scheme, urlHolder.host, urlHolder.port)
   436  			canonicalizedURL = "/"
   437  		} else {
   438  			if conf.pathStyle {
   439  				requestURL = fmt.Sprintf("%s://%s:%d/%s", urlHolder.scheme, urlHolder.host, urlHolder.port, bucketName)
   440  				canonicalizedURL = "/" + bucketName
   441  			} else {
   442  				requestURL = fmt.Sprintf("%s://%s.%s:%d", urlHolder.scheme, bucketName, urlHolder.host, urlHolder.port)
   443  				if conf.signature == "v2" || conf.signature == "OBS" {
   444  					canonicalizedURL = "/" + bucketName + "/"
   445  				} else {
   446  					canonicalizedURL = "/"
   447  				}
   448  			}
   449  		}
   450  	}
   451  	return
   452  }
   453  
   454  func (conf *config) prepareObjectKey(escape bool, objectKey string, escapeFunc func(s string) string) (encodeObjectKey string) {
   455  	if escape {
   456  		tempKey := []rune(objectKey)
   457  		result := make([]string, 0, len(tempKey))
   458  		for _, value := range tempKey {
   459  			if string(value) == "/" {
   460  				result = append(result, string(value))
   461  			} else {
   462  				if string(value) == " " {
   463  					result = append(result, url.PathEscape(string(value)))
   464  				} else {
   465  					result = append(result, url.QueryEscape(string(value)))
   466  				}
   467  			}
   468  		}
   469  		encodeObjectKey = strings.Join(result, "")
   470  	} else {
   471  		encodeObjectKey = escapeFunc(objectKey)
   472  	}
   473  	return
   474  }
   475  
   476  func (conf *config) prepareEscapeFunc(escape bool) (escapeFunc func(s string) string) {
   477  	if escape {
   478  		return url.QueryEscape
   479  	}
   480  	return DummyQueryEscape
   481  }
   482  
   483  func (conf *config) formatUrls(bucketName, objectKey string, params map[string]string, escape bool) (requestURL string, canonicalizedURL string) {
   484  
   485  	requestURL, canonicalizedURL = conf.prepareBaseURL(bucketName)
   486  	var escapeFunc func(s string) string
   487  	escapeFunc = conf.prepareEscapeFunc(escape)
   488  
   489  	if objectKey != "" {
   490  		var encodeObjectKey string
   491  		encodeObjectKey = conf.prepareObjectKey(escape, objectKey, escapeFunc)
   492  		requestURL += "/" + encodeObjectKey
   493  		if !strings.HasSuffix(canonicalizedURL, "/") {
   494  			canonicalizedURL += "/"
   495  		}
   496  		canonicalizedURL += encodeObjectKey
   497  	}
   498  
   499  	keys := make([]string, 0, len(params))
   500  	for key := range params {
   501  		keys = append(keys, strings.TrimSpace(key))
   502  	}
   503  	sort.Strings(keys)
   504  	i := 0
   505  
   506  	for index, key := range keys {
   507  		if index == 0 {
   508  			requestURL += "?"
   509  		} else {
   510  			requestURL += "&"
   511  		}
   512  		_key := url.QueryEscape(key)
   513  		requestURL += _key
   514  
   515  		_value := params[key]
   516  		if conf.signature == "v4" {
   517  			requestURL += "=" + url.QueryEscape(_value)
   518  		} else {
   519  			if _value != "" {
   520  				requestURL += "=" + url.QueryEscape(_value)
   521  				_value = "=" + _value
   522  			} else {
   523  				_value = ""
   524  			}
   525  			lowerKey := strings.ToLower(key)
   526  			_, ok := allowedResourceParameterNames[lowerKey]
   527  			prefixHeader := HEADER_PREFIX
   528  			isObs := conf.signature == SignatureObs
   529  			if isObs {
   530  				prefixHeader = HEADER_PREFIX_OBS
   531  			}
   532  			ok = ok || strings.HasPrefix(lowerKey, prefixHeader)
   533  			if ok {
   534  				if i == 0 {
   535  					canonicalizedURL += "?"
   536  				} else {
   537  					canonicalizedURL += "&"
   538  				}
   539  				canonicalizedURL += getQueryURL(_key, _value)
   540  				i++
   541  			}
   542  		}
   543  	}
   544  	return
   545  }
   546  
   547  func getQueryURL(key, value string) string {
   548  	queryURL := ""
   549  	queryURL += key
   550  	queryURL += value
   551  	return queryURL
   552  }
   553  
   554  func (obsClient ObsClient) getProgressListener(extensions []extensionOptions) ProgressListener {
   555  	for _, extension := range extensions {
   556  		if configure, ok := extension.(extensionProgressListener); ok {
   557  			return configure()
   558  		}
   559  	}
   560  	return nil
   561  }