|
- // Copyright 2019 Huawei Technologies Co.,Ltd.
- // 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.
-
- //nolint:golint, unused
- package obs
-
- import (
- "context"
- "crypto/tls"
- "crypto/x509"
- "errors"
- "fmt"
- "net"
- "net/http"
- "net/url"
- "sort"
- "strconv"
- "strings"
- "time"
- )
-
- type securityProvider struct {
- ak string
- sk string
- securityToken string
- }
-
- type urlHolder struct {
- scheme string
- host string
- port int
- }
-
- type config struct {
- securityProvider *securityProvider
- urlHolder *urlHolder
- pathStyle bool
- cname bool
- sslVerify bool
- endpoint string
- signature SignatureType
- region string
- connectTimeout int
- socketTimeout int
- headerTimeout int
- idleConnTimeout int
- finalTimeout int
- maxRetryCount int
- proxyURL string
- maxConnsPerHost int
- pemCerts []byte
- transport *http.Transport
- ctx context.Context
- maxRedirectCount int
- }
-
- func (conf config) String() string {
- return fmt.Sprintf("[endpoint:%s, signature:%s, pathStyle:%v, region:%s"+
- "\nconnectTimeout:%d, socketTimeout:%dheaderTimeout:%d, idleConnTimeout:%d"+
- "\nmaxRetryCount:%d, maxConnsPerHost:%d, sslVerify:%v, maxRedirectCount:%d]",
- conf.endpoint, conf.signature, conf.pathStyle, conf.region,
- conf.connectTimeout, conf.socketTimeout, conf.headerTimeout, conf.idleConnTimeout,
- conf.maxRetryCount, conf.maxConnsPerHost, conf.sslVerify, conf.maxRedirectCount,
- )
- }
-
- type configurer func(conf *config)
-
- // WithSslVerify is a wrapper for WithSslVerifyAndPemCerts.
- func WithSslVerify(sslVerify bool) configurer {
- return WithSslVerifyAndPemCerts(sslVerify, nil)
- }
-
- // WithSslVerifyAndPemCerts is a configurer for ObsClient to set conf.sslVerify and conf.pemCerts.
- func WithSslVerifyAndPemCerts(sslVerify bool, pemCerts []byte) configurer {
- return func(conf *config) {
- conf.sslVerify = sslVerify
- conf.pemCerts = pemCerts
- }
- }
-
- // WithHeaderTimeout is a configurer for ObsClient to set the timeout period of obtaining the response headers.
- func WithHeaderTimeout(headerTimeout int) configurer {
- return func(conf *config) {
- conf.headerTimeout = headerTimeout
- }
- }
-
- // WithProxyUrl is a configurer for ObsClient to set HTTP proxy.
- func WithProxyUrl(proxyURL string) configurer {
- return func(conf *config) {
- conf.proxyURL = proxyURL
- }
- }
-
- // WithMaxConnections is a configurer for ObsClient to set the maximum number of idle HTTP connections.
- func WithMaxConnections(maxConnsPerHost int) configurer {
- return func(conf *config) {
- conf.maxConnsPerHost = maxConnsPerHost
- }
- }
-
- // WithPathStyle is a configurer for ObsClient.
- func WithPathStyle(pathStyle bool) configurer {
- return func(conf *config) {
- conf.pathStyle = pathStyle
- }
- }
-
- // WithSignature is a configurer for ObsClient.
- func WithSignature(signature SignatureType) configurer {
- return func(conf *config) {
- conf.signature = signature
- }
- }
-
- // WithRegion is a configurer for ObsClient.
- func WithRegion(region string) configurer {
- return func(conf *config) {
- conf.region = region
- }
- }
-
- // WithConnectTimeout is a configurer for ObsClient to set timeout period for establishing
- // an http/https connection, in seconds.
- func WithConnectTimeout(connectTimeout int) configurer {
- return func(conf *config) {
- conf.connectTimeout = connectTimeout
- }
- }
-
- // WithSocketTimeout is a configurer for ObsClient to set the timeout duration for transmitting data at
- // the socket layer, in seconds.
- func WithSocketTimeout(socketTimeout int) configurer {
- return func(conf *config) {
- conf.socketTimeout = socketTimeout
- }
- }
-
- // WithIdleConnTimeout is a configurer for ObsClient to set the timeout period of an idle HTTP connection
- // in the connection pool, in seconds.
- func WithIdleConnTimeout(idleConnTimeout int) configurer {
- return func(conf *config) {
- conf.idleConnTimeout = idleConnTimeout
- }
- }
-
- // WithMaxRetryCount is a configurer for ObsClient to set the maximum number of retries when an HTTP/HTTPS connection is abnormal.
- func WithMaxRetryCount(maxRetryCount int) configurer {
- return func(conf *config) {
- conf.maxRetryCount = maxRetryCount
- }
- }
-
- // WithSecurityToken is a configurer for ObsClient to set the security token in the temporary access keys.
- func WithSecurityToken(securityToken string) configurer {
- return func(conf *config) {
- conf.securityProvider.securityToken = securityToken
- }
- }
-
- // WithHttpTransport is a configurer for ObsClient to set the customized http Transport.
- func WithHttpTransport(transport *http.Transport) configurer {
- return func(conf *config) {
- conf.transport = transport
- }
- }
-
- // WithRequestContext is a configurer for ObsClient to set the context for each HTTP request.
- func WithRequestContext(ctx context.Context) configurer {
- return func(conf *config) {
- conf.ctx = ctx
- }
- }
-
- // WithCustomDomainName is a configurer for ObsClient.
- func WithCustomDomainName(cname bool) configurer {
- return func(conf *config) {
- conf.cname = cname
- }
- }
-
- // WithMaxRedirectCount is a configurer for ObsClient to set the maximum number of times that the request is redirected.
- func WithMaxRedirectCount(maxRedirectCount int) configurer {
- return func(conf *config) {
- conf.maxRedirectCount = maxRedirectCount
- }
- }
-
- func (conf *config) prepareConfig() {
- if conf.connectTimeout <= 0 {
- conf.connectTimeout = DEFAULT_CONNECT_TIMEOUT
- }
-
- if conf.socketTimeout <= 0 {
- conf.socketTimeout = DEFAULT_SOCKET_TIMEOUT
- }
-
- conf.finalTimeout = conf.socketTimeout * 10
-
- if conf.headerTimeout <= 0 {
- conf.headerTimeout = DEFAULT_HEADER_TIMEOUT
- }
-
- if conf.idleConnTimeout < 0 {
- conf.idleConnTimeout = DEFAULT_IDLE_CONN_TIMEOUT
- }
-
- if conf.maxRetryCount < 0 {
- conf.maxRetryCount = DEFAULT_MAX_RETRY_COUNT
- }
-
- if conf.maxConnsPerHost <= 0 {
- conf.maxConnsPerHost = DEFAULT_MAX_CONN_PER_HOST
- }
-
- if conf.maxRedirectCount < 0 {
- conf.maxRedirectCount = DEFAULT_MAX_REDIRECT_COUNT
- }
- }
-
- func (conf *config) initConfigWithDefault() error {
- conf.securityProvider.ak = strings.TrimSpace(conf.securityProvider.ak)
- conf.securityProvider.sk = strings.TrimSpace(conf.securityProvider.sk)
- conf.securityProvider.securityToken = strings.TrimSpace(conf.securityProvider.securityToken)
- conf.endpoint = strings.TrimSpace(conf.endpoint)
- if conf.endpoint == "" {
- return errors.New("endpoint is not set")
- }
-
- if index := strings.Index(conf.endpoint, "?"); index > 0 {
- conf.endpoint = conf.endpoint[:index]
- }
-
- for strings.LastIndex(conf.endpoint, "/") == len(conf.endpoint)-1 {
- conf.endpoint = conf.endpoint[:len(conf.endpoint)-1]
- }
-
- if conf.signature == "" {
- conf.signature = DEFAULT_SIGNATURE
- }
-
- urlHolder := &urlHolder{}
- var address string
- if strings.HasPrefix(conf.endpoint, "https://") {
- urlHolder.scheme = "https"
- address = conf.endpoint[len("https://"):]
- } else if strings.HasPrefix(conf.endpoint, "http://") {
- urlHolder.scheme = "http"
- address = conf.endpoint[len("http://"):]
- } else {
- urlHolder.scheme = "https"
- address = conf.endpoint
- }
-
- addr := strings.Split(address, ":")
- if len(addr) == 2 {
- if port, err := strconv.Atoi(addr[1]); err == nil {
- urlHolder.port = port
- }
- }
- urlHolder.host = addr[0]
- if urlHolder.port == 0 {
- if urlHolder.scheme == "https" {
- urlHolder.port = 443
- } else {
- urlHolder.port = 80
- }
- }
-
- if IsIP(urlHolder.host) {
- conf.pathStyle = true
- }
-
- conf.urlHolder = urlHolder
-
- conf.region = strings.TrimSpace(conf.region)
- if conf.region == "" {
- conf.region = DEFAULT_REGION
- }
-
- conf.prepareConfig()
- conf.proxyURL = strings.TrimSpace(conf.proxyURL)
- return nil
- }
-
- func (conf *config) getTransport() error {
- if conf.transport == nil {
- conf.transport = &http.Transport{
- Dial: func(network, addr string) (net.Conn, error) {
- conn, err := net.DialTimeout(network, addr, time.Second*time.Duration(conf.connectTimeout))
- if err != nil {
- return nil, err
- }
- return getConnDelegate(conn, conf.socketTimeout, conf.finalTimeout), nil
- },
- MaxIdleConns: conf.maxConnsPerHost,
- MaxIdleConnsPerHost: conf.maxConnsPerHost,
- ResponseHeaderTimeout: time.Second * time.Duration(conf.headerTimeout),
- IdleConnTimeout: time.Second * time.Duration(conf.idleConnTimeout),
- }
-
- if conf.proxyURL != "" {
- proxyURL, err := url.Parse(conf.proxyURL)
- if err != nil {
- return err
- }
- conf.transport.Proxy = http.ProxyURL(proxyURL)
- }
-
- tlsConfig := &tls.Config{InsecureSkipVerify: !conf.sslVerify}
- if conf.sslVerify && conf.pemCerts != nil {
- pool := x509.NewCertPool()
- pool.AppendCertsFromPEM(conf.pemCerts)
- tlsConfig.RootCAs = pool
- }
-
- conf.transport.TLSClientConfig = tlsConfig
- conf.transport.DisableCompression = true
- }
-
- return nil
- }
-
- func checkRedirectFunc(req *http.Request, via []*http.Request) error {
- return http.ErrUseLastResponse
- }
-
- // DummyQueryEscape return the input string.
- func DummyQueryEscape(s string) string {
- return s
- }
-
- func (conf *config) prepareBaseURL(bucketName string) (requestURL string, canonicalizedURL string) {
- urlHolder := conf.urlHolder
- if conf.cname {
- requestURL = fmt.Sprintf("%s://%s:%d", urlHolder.scheme, urlHolder.host, urlHolder.port)
- if conf.signature == "v4" {
- canonicalizedURL = "/"
- } else {
- canonicalizedURL = "/" + urlHolder.host + "/"
- }
- } else {
- if bucketName == "" {
- requestURL = fmt.Sprintf("%s://%s:%d", urlHolder.scheme, urlHolder.host, urlHolder.port)
- canonicalizedURL = "/"
- } else {
- if conf.pathStyle {
- requestURL = fmt.Sprintf("%s://%s:%d/%s", urlHolder.scheme, urlHolder.host, urlHolder.port, bucketName)
- canonicalizedURL = "/" + bucketName
- } else {
- requestURL = fmt.Sprintf("%s://%s.%s:%d", urlHolder.scheme, bucketName, urlHolder.host, urlHolder.port)
- if conf.signature == "v2" || conf.signature == "OBS" {
- canonicalizedURL = "/" + bucketName + "/"
- } else {
- canonicalizedURL = "/"
- }
- }
- }
- }
- return
- }
-
- func (conf *config) prepareObjectKey(escape bool, objectKey string, escapeFunc func(s string) string) (encodeObjectKey string) {
- if escape {
- tempKey := []rune(objectKey)
- result := make([]string, 0, len(tempKey))
- for _, value := range tempKey {
- if string(value) == "/" {
- result = append(result, string(value))
- } else {
- if string(value) == " " {
- result = append(result, url.PathEscape(string(value)))
- } else {
- result = append(result, url.QueryEscape(string(value)))
- }
- }
- }
- encodeObjectKey = strings.Join(result, "")
- } else {
- encodeObjectKey = escapeFunc(objectKey)
- }
- return
- }
-
- func (conf *config) prepareEscapeFunc(escape bool) (escapeFunc func(s string) string) {
- if escape {
- return url.QueryEscape
- }
- return DummyQueryEscape
- }
-
- func (conf *config) formatUrls(bucketName, objectKey string, params map[string]string, escape bool) (requestURL string, canonicalizedURL string) {
-
- requestURL, canonicalizedURL = conf.prepareBaseURL(bucketName)
- var escapeFunc func(s string) string
- escapeFunc = conf.prepareEscapeFunc(escape)
-
- if objectKey != "" {
- var encodeObjectKey string
- encodeObjectKey = conf.prepareObjectKey(escape, objectKey, escapeFunc)
- requestURL += "/" + encodeObjectKey
- if !strings.HasSuffix(canonicalizedURL, "/") {
- canonicalizedURL += "/"
- }
- canonicalizedURL += encodeObjectKey
- }
-
- keys := make([]string, 0, len(params))
- for key := range params {
- keys = append(keys, strings.TrimSpace(key))
- }
- sort.Strings(keys)
- i := 0
-
- for index, key := range keys {
- if index == 0 {
- requestURL += "?"
- } else {
- requestURL += "&"
- }
- _key := url.QueryEscape(key)
- requestURL += _key
-
- _value := params[key]
- if conf.signature == "v4" {
- requestURL += "=" + url.QueryEscape(_value)
- } else {
- if _value != "" {
- requestURL += "=" + url.QueryEscape(_value)
- _value = "=" + _value
- } else {
- _value = ""
- }
- lowerKey := strings.ToLower(key)
- _, ok := allowedResourceParameterNames[lowerKey]
- prefixHeader := HEADER_PREFIX
- isObs := conf.signature == SignatureObs
- if isObs {
- prefixHeader = HEADER_PREFIX_OBS
- }
- ok = ok || strings.HasPrefix(lowerKey, prefixHeader)
- if ok {
- if i == 0 {
- canonicalizedURL += "?"
- } else {
- canonicalizedURL += "&"
- }
- canonicalizedURL += getQueryURL(_key, _value)
- i++
- }
- }
- }
- return
- }
-
- func getQueryURL(key, value string) string {
- queryURL := ""
- queryURL += key
- queryURL += value
- return queryURL
- }
|