yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/huawei/obs/conf.go (about)

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