You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
430 lines
10 KiB
430 lines
10 KiB
// 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.
|
|
|
|
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
|
|
endpoint string
|
|
signature SignatureType
|
|
pathStyle bool
|
|
region string
|
|
connectTimeout int
|
|
socketTimeout int
|
|
headerTimeout int
|
|
idleConnTimeout int
|
|
finalTimeout int
|
|
maxRetryCount int
|
|
proxyUrl string
|
|
maxConnsPerHost int
|
|
sslVerify bool
|
|
pemCerts []byte
|
|
transport *http.Transport
|
|
ctx context.Context
|
|
cname bool
|
|
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, proxyUrl:%s, 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.proxyUrl, conf.maxRedirectCount,
|
|
)
|
|
}
|
|
|
|
type configurer func(conf *config)
|
|
|
|
func WithSslVerify(sslVerify bool) configurer {
|
|
return WithSslVerifyAndPemCerts(sslVerify, nil)
|
|
}
|
|
|
|
func WithSslVerifyAndPemCerts(sslVerify bool, pemCerts []byte) configurer {
|
|
return func(conf *config) {
|
|
conf.sslVerify = sslVerify
|
|
conf.pemCerts = pemCerts
|
|
}
|
|
}
|
|
|
|
func WithHeaderTimeout(headerTimeout int) configurer {
|
|
return func(conf *config) {
|
|
conf.headerTimeout = headerTimeout
|
|
}
|
|
}
|
|
|
|
func WithProxyUrl(proxyUrl string) configurer {
|
|
return func(conf *config) {
|
|
conf.proxyUrl = proxyUrl
|
|
}
|
|
}
|
|
|
|
func WithMaxConnections(maxConnsPerHost int) configurer {
|
|
return func(conf *config) {
|
|
conf.maxConnsPerHost = maxConnsPerHost
|
|
}
|
|
}
|
|
|
|
func WithPathStyle(pathStyle bool) configurer {
|
|
return func(conf *config) {
|
|
conf.pathStyle = pathStyle
|
|
}
|
|
}
|
|
|
|
func WithSignature(signature SignatureType) configurer {
|
|
return func(conf *config) {
|
|
conf.signature = signature
|
|
}
|
|
}
|
|
|
|
func WithRegion(region string) configurer {
|
|
return func(conf *config) {
|
|
conf.region = region
|
|
}
|
|
}
|
|
|
|
func WithConnectTimeout(connectTimeout int) configurer {
|
|
return func(conf *config) {
|
|
conf.connectTimeout = connectTimeout
|
|
}
|
|
}
|
|
|
|
func WithSocketTimeout(socketTimeout int) configurer {
|
|
return func(conf *config) {
|
|
conf.socketTimeout = socketTimeout
|
|
}
|
|
}
|
|
|
|
func WithIdleConnTimeout(idleConnTimeout int) configurer {
|
|
return func(conf *config) {
|
|
conf.idleConnTimeout = idleConnTimeout
|
|
}
|
|
}
|
|
|
|
func WithMaxRetryCount(maxRetryCount int) configurer {
|
|
return func(conf *config) {
|
|
conf.maxRetryCount = maxRetryCount
|
|
}
|
|
}
|
|
|
|
func WithSecurityToken(securityToken string) configurer {
|
|
return func(conf *config) {
|
|
conf.securityProvider.securityToken = securityToken
|
|
}
|
|
}
|
|
|
|
func WithHttpTransport(transport *http.Transport) configurer {
|
|
return func(conf *config) {
|
|
conf.transport = transport
|
|
}
|
|
}
|
|
|
|
func WithRequestContext(ctx context.Context) configurer {
|
|
return func(conf *config) {
|
|
conf.ctx = ctx
|
|
}
|
|
}
|
|
|
|
func WithCustomDomainName(cname bool) configurer {
|
|
return func(conf *config) {
|
|
conf.cname = cname
|
|
}
|
|
}
|
|
|
|
func WithMaxRedirectCount(maxRedirectCount int) configurer {
|
|
return func(conf *config) {
|
|
conf.maxRedirectCount = maxRedirectCount
|
|
}
|
|
}
|
|
|
|
|
|
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 = "http"
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkRedirectFunc(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
}
|
|
|
|
func DummyQueryEscape(s string) string {
|
|
return s
|
|
}
|
|
|
|
func (conf *config) formatUrls(bucketName, objectKey string, params map[string]string, escape bool) (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 = "/"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var escapeFunc func(s string) string
|
|
if escape {
|
|
escapeFunc = url.QueryEscape
|
|
} else {
|
|
escapeFunc = DummyQueryEscape
|
|
}
|
|
|
|
if objectKey != "" {
|
|
var 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{
|
|
result = append(result, url.QueryEscape(string(value)))
|
|
}
|
|
}
|
|
encodeObjectKey = strings.Join(result,"")
|
|
}else{
|
|
encodeObjectKey = escapeFunc(objectKey)
|
|
}
|
|
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 := allowed_resource_parameter_names[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
|
|
}
|