You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

4 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  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. //nolint:golint, unused
  13. package obs
  14. import (
  15. "context"
  16. "crypto/tls"
  17. "crypto/x509"
  18. "errors"
  19. "fmt"
  20. "net"
  21. "net/http"
  22. "net/url"
  23. "sort"
  24. "strconv"
  25. "strings"
  26. "time"
  27. )
  28. type securityProvider struct {
  29. ak string
  30. sk string
  31. securityToken string
  32. }
  33. type urlHolder struct {
  34. scheme string
  35. host string
  36. port int
  37. }
  38. type config struct {
  39. securityProvider *securityProvider
  40. urlHolder *urlHolder
  41. pathStyle bool
  42. cname bool
  43. sslVerify bool
  44. endpoint string
  45. signature SignatureType
  46. region string
  47. connectTimeout int
  48. socketTimeout int
  49. headerTimeout int
  50. idleConnTimeout int
  51. finalTimeout int
  52. maxRetryCount int
  53. proxyURL string
  54. maxConnsPerHost int
  55. pemCerts []byte
  56. transport *http.Transport
  57. ctx context.Context
  58. maxRedirectCount int
  59. }
  60. func (conf config) String() string {
  61. return fmt.Sprintf("[endpoint:%s, signature:%s, pathStyle:%v, region:%s"+
  62. "\nconnectTimeout:%d, socketTimeout:%dheaderTimeout:%d, idleConnTimeout:%d"+
  63. "\nmaxRetryCount:%d, maxConnsPerHost:%d, sslVerify:%v, maxRedirectCount:%d]",
  64. conf.endpoint, conf.signature, conf.pathStyle, conf.region,
  65. conf.connectTimeout, conf.socketTimeout, conf.headerTimeout, conf.idleConnTimeout,
  66. conf.maxRetryCount, conf.maxConnsPerHost, conf.sslVerify, conf.maxRedirectCount,
  67. )
  68. }
  69. type configurer func(conf *config)
  70. // WithSslVerify is a wrapper for WithSslVerifyAndPemCerts.
  71. func WithSslVerify(sslVerify bool) configurer {
  72. return WithSslVerifyAndPemCerts(sslVerify, nil)
  73. }
  74. // WithSslVerifyAndPemCerts is a configurer for ObsClient to set conf.sslVerify and conf.pemCerts.
  75. func WithSslVerifyAndPemCerts(sslVerify bool, pemCerts []byte) configurer {
  76. return func(conf *config) {
  77. conf.sslVerify = sslVerify
  78. conf.pemCerts = pemCerts
  79. }
  80. }
  81. // WithHeaderTimeout is a configurer for ObsClient to set the timeout period of obtaining the response headers.
  82. func WithHeaderTimeout(headerTimeout int) configurer {
  83. return func(conf *config) {
  84. conf.headerTimeout = headerTimeout
  85. }
  86. }
  87. // WithProxyUrl is a configurer for ObsClient to set HTTP proxy.
  88. func WithProxyUrl(proxyURL string) configurer {
  89. return func(conf *config) {
  90. conf.proxyURL = proxyURL
  91. }
  92. }
  93. // WithMaxConnections is a configurer for ObsClient to set the maximum number of idle HTTP connections.
  94. func WithMaxConnections(maxConnsPerHost int) configurer {
  95. return func(conf *config) {
  96. conf.maxConnsPerHost = maxConnsPerHost
  97. }
  98. }
  99. // WithPathStyle is a configurer for ObsClient.
  100. func WithPathStyle(pathStyle bool) configurer {
  101. return func(conf *config) {
  102. conf.pathStyle = pathStyle
  103. }
  104. }
  105. // WithSignature is a configurer for ObsClient.
  106. func WithSignature(signature SignatureType) configurer {
  107. return func(conf *config) {
  108. conf.signature = signature
  109. }
  110. }
  111. // WithRegion is a configurer for ObsClient.
  112. func WithRegion(region string) configurer {
  113. return func(conf *config) {
  114. conf.region = region
  115. }
  116. }
  117. // WithConnectTimeout is a configurer for ObsClient to set timeout period for establishing
  118. // an http/https connection, in seconds.
  119. func WithConnectTimeout(connectTimeout int) configurer {
  120. return func(conf *config) {
  121. conf.connectTimeout = connectTimeout
  122. }
  123. }
  124. // WithSocketTimeout is a configurer for ObsClient to set the timeout duration for transmitting data at
  125. // the socket layer, in seconds.
  126. func WithSocketTimeout(socketTimeout int) configurer {
  127. return func(conf *config) {
  128. conf.socketTimeout = socketTimeout
  129. }
  130. }
  131. // WithIdleConnTimeout is a configurer for ObsClient to set the timeout period of an idle HTTP connection
  132. // in the connection pool, in seconds.
  133. func WithIdleConnTimeout(idleConnTimeout int) configurer {
  134. return func(conf *config) {
  135. conf.idleConnTimeout = idleConnTimeout
  136. }
  137. }
  138. // WithMaxRetryCount is a configurer for ObsClient to set the maximum number of retries when an HTTP/HTTPS connection is abnormal.
  139. func WithMaxRetryCount(maxRetryCount int) configurer {
  140. return func(conf *config) {
  141. conf.maxRetryCount = maxRetryCount
  142. }
  143. }
  144. // WithSecurityToken is a configurer for ObsClient to set the security token in the temporary access keys.
  145. func WithSecurityToken(securityToken string) configurer {
  146. return func(conf *config) {
  147. conf.securityProvider.securityToken = securityToken
  148. }
  149. }
  150. // WithHttpTransport is a configurer for ObsClient to set the customized http Transport.
  151. func WithHttpTransport(transport *http.Transport) configurer {
  152. return func(conf *config) {
  153. conf.transport = transport
  154. }
  155. }
  156. // WithRequestContext is a configurer for ObsClient to set the context for each HTTP request.
  157. func WithRequestContext(ctx context.Context) configurer {
  158. return func(conf *config) {
  159. conf.ctx = ctx
  160. }
  161. }
  162. // WithCustomDomainName is a configurer for ObsClient.
  163. func WithCustomDomainName(cname bool) configurer {
  164. return func(conf *config) {
  165. conf.cname = cname
  166. }
  167. }
  168. // WithMaxRedirectCount is a configurer for ObsClient to set the maximum number of times that the request is redirected.
  169. func WithMaxRedirectCount(maxRedirectCount int) configurer {
  170. return func(conf *config) {
  171. conf.maxRedirectCount = maxRedirectCount
  172. }
  173. }
  174. func (conf *config) prepareConfig() {
  175. if conf.connectTimeout <= 0 {
  176. conf.connectTimeout = DEFAULT_CONNECT_TIMEOUT
  177. }
  178. if conf.socketTimeout <= 0 {
  179. conf.socketTimeout = DEFAULT_SOCKET_TIMEOUT
  180. }
  181. conf.finalTimeout = conf.socketTimeout * 10
  182. if conf.headerTimeout <= 0 {
  183. conf.headerTimeout = DEFAULT_HEADER_TIMEOUT
  184. }
  185. if conf.idleConnTimeout < 0 {
  186. conf.idleConnTimeout = DEFAULT_IDLE_CONN_TIMEOUT
  187. }
  188. if conf.maxRetryCount < 0 {
  189. conf.maxRetryCount = DEFAULT_MAX_RETRY_COUNT
  190. }
  191. if conf.maxConnsPerHost <= 0 {
  192. conf.maxConnsPerHost = DEFAULT_MAX_CONN_PER_HOST
  193. }
  194. if conf.maxRedirectCount < 0 {
  195. conf.maxRedirectCount = DEFAULT_MAX_REDIRECT_COUNT
  196. }
  197. }
  198. func (conf *config) initConfigWithDefault() error {
  199. conf.securityProvider.ak = strings.TrimSpace(conf.securityProvider.ak)
  200. conf.securityProvider.sk = strings.TrimSpace(conf.securityProvider.sk)
  201. conf.securityProvider.securityToken = strings.TrimSpace(conf.securityProvider.securityToken)
  202. conf.endpoint = strings.TrimSpace(conf.endpoint)
  203. if conf.endpoint == "" {
  204. return errors.New("endpoint is not set")
  205. }
  206. if index := strings.Index(conf.endpoint, "?"); index > 0 {
  207. conf.endpoint = conf.endpoint[:index]
  208. }
  209. for strings.LastIndex(conf.endpoint, "/") == len(conf.endpoint)-1 {
  210. conf.endpoint = conf.endpoint[:len(conf.endpoint)-1]
  211. }
  212. if conf.signature == "" {
  213. conf.signature = DEFAULT_SIGNATURE
  214. }
  215. urlHolder := &urlHolder{}
  216. var address string
  217. if strings.HasPrefix(conf.endpoint, "https://") {
  218. urlHolder.scheme = "https"
  219. address = conf.endpoint[len("https://"):]
  220. } else if strings.HasPrefix(conf.endpoint, "http://") {
  221. urlHolder.scheme = "http"
  222. address = conf.endpoint[len("http://"):]
  223. } else {
  224. urlHolder.scheme = "https"
  225. address = conf.endpoint
  226. }
  227. addr := strings.Split(address, ":")
  228. if len(addr) == 2 {
  229. if port, err := strconv.Atoi(addr[1]); err == nil {
  230. urlHolder.port = port
  231. }
  232. }
  233. urlHolder.host = addr[0]
  234. if urlHolder.port == 0 {
  235. if urlHolder.scheme == "https" {
  236. urlHolder.port = 443
  237. } else {
  238. urlHolder.port = 80
  239. }
  240. }
  241. if IsIP(urlHolder.host) {
  242. conf.pathStyle = true
  243. }
  244. conf.urlHolder = urlHolder
  245. conf.region = strings.TrimSpace(conf.region)
  246. if conf.region == "" {
  247. conf.region = DEFAULT_REGION
  248. }
  249. conf.prepareConfig()
  250. conf.proxyURL = strings.TrimSpace(conf.proxyURL)
  251. return nil
  252. }
  253. func (conf *config) getTransport() error {
  254. if conf.transport == nil {
  255. conf.transport = &http.Transport{
  256. Dial: func(network, addr string) (net.Conn, error) {
  257. conn, err := net.DialTimeout(network, addr, time.Second*time.Duration(conf.connectTimeout))
  258. if err != nil {
  259. return nil, err
  260. }
  261. return getConnDelegate(conn, conf.socketTimeout, conf.finalTimeout), nil
  262. },
  263. MaxIdleConns: conf.maxConnsPerHost,
  264. MaxIdleConnsPerHost: conf.maxConnsPerHost,
  265. ResponseHeaderTimeout: time.Second * time.Duration(conf.headerTimeout),
  266. IdleConnTimeout: time.Second * time.Duration(conf.idleConnTimeout),
  267. }
  268. if conf.proxyURL != "" {
  269. proxyURL, err := url.Parse(conf.proxyURL)
  270. if err != nil {
  271. return err
  272. }
  273. conf.transport.Proxy = http.ProxyURL(proxyURL)
  274. }
  275. tlsConfig := &tls.Config{InsecureSkipVerify: !conf.sslVerify}
  276. if conf.sslVerify && conf.pemCerts != nil {
  277. pool := x509.NewCertPool()
  278. pool.AppendCertsFromPEM(conf.pemCerts)
  279. tlsConfig.RootCAs = pool
  280. }
  281. conf.transport.TLSClientConfig = tlsConfig
  282. conf.transport.DisableCompression = true
  283. }
  284. return nil
  285. }
  286. func checkRedirectFunc(req *http.Request, via []*http.Request) error {
  287. return http.ErrUseLastResponse
  288. }
  289. // DummyQueryEscape return the input string.
  290. func DummyQueryEscape(s string) string {
  291. return s
  292. }
  293. func (conf *config) prepareBaseURL(bucketName string) (requestURL string, canonicalizedURL string) {
  294. urlHolder := conf.urlHolder
  295. if conf.cname {
  296. requestURL = fmt.Sprintf("%s://%s:%d", urlHolder.scheme, urlHolder.host, urlHolder.port)
  297. if conf.signature == "v4" {
  298. canonicalizedURL = "/"
  299. } else {
  300. canonicalizedURL = "/" + urlHolder.host + "/"
  301. }
  302. } else {
  303. if bucketName == "" {
  304. requestURL = fmt.Sprintf("%s://%s:%d", urlHolder.scheme, urlHolder.host, urlHolder.port)
  305. canonicalizedURL = "/"
  306. } else {
  307. if conf.pathStyle {
  308. requestURL = fmt.Sprintf("%s://%s:%d/%s", urlHolder.scheme, urlHolder.host, urlHolder.port, bucketName)
  309. canonicalizedURL = "/" + bucketName
  310. } else {
  311. requestURL = fmt.Sprintf("%s://%s.%s:%d", urlHolder.scheme, bucketName, urlHolder.host, urlHolder.port)
  312. if conf.signature == "v2" || conf.signature == "OBS" {
  313. canonicalizedURL = "/" + bucketName + "/"
  314. } else {
  315. canonicalizedURL = "/"
  316. }
  317. }
  318. }
  319. }
  320. return
  321. }
  322. func (conf *config) prepareObjectKey(escape bool, objectKey string, escapeFunc func(s string) string) (encodeObjectKey string) {
  323. if escape {
  324. tempKey := []rune(objectKey)
  325. result := make([]string, 0, len(tempKey))
  326. for _, value := range tempKey {
  327. if string(value) == "/" {
  328. result = append(result, string(value))
  329. } else {
  330. if string(value) == " " {
  331. result = append(result, url.PathEscape(string(value)))
  332. } else {
  333. result = append(result, url.QueryEscape(string(value)))
  334. }
  335. }
  336. }
  337. encodeObjectKey = strings.Join(result, "")
  338. } else {
  339. encodeObjectKey = escapeFunc(objectKey)
  340. }
  341. return
  342. }
  343. func (conf *config) prepareEscapeFunc(escape bool) (escapeFunc func(s string) string) {
  344. if escape {
  345. return url.QueryEscape
  346. }
  347. return DummyQueryEscape
  348. }
  349. func (conf *config) formatUrls(bucketName, objectKey string, params map[string]string, escape bool) (requestURL string, canonicalizedURL string) {
  350. requestURL, canonicalizedURL = conf.prepareBaseURL(bucketName)
  351. var escapeFunc func(s string) string
  352. escapeFunc = conf.prepareEscapeFunc(escape)
  353. if objectKey != "" {
  354. var encodeObjectKey string
  355. encodeObjectKey = conf.prepareObjectKey(escape, objectKey, escapeFunc)
  356. requestURL += "/" + encodeObjectKey
  357. if !strings.HasSuffix(canonicalizedURL, "/") {
  358. canonicalizedURL += "/"
  359. }
  360. canonicalizedURL += encodeObjectKey
  361. }
  362. keys := make([]string, 0, len(params))
  363. for key := range params {
  364. keys = append(keys, strings.TrimSpace(key))
  365. }
  366. sort.Strings(keys)
  367. i := 0
  368. for index, key := range keys {
  369. if index == 0 {
  370. requestURL += "?"
  371. } else {
  372. requestURL += "&"
  373. }
  374. _key := url.QueryEscape(key)
  375. requestURL += _key
  376. _value := params[key]
  377. if conf.signature == "v4" {
  378. requestURL += "=" + url.QueryEscape(_value)
  379. } else {
  380. if _value != "" {
  381. requestURL += "=" + url.QueryEscape(_value)
  382. _value = "=" + _value
  383. } else {
  384. _value = ""
  385. }
  386. lowerKey := strings.ToLower(key)
  387. _, ok := allowedResourceParameterNames[lowerKey]
  388. prefixHeader := HEADER_PREFIX
  389. isObs := conf.signature == SignatureObs
  390. if isObs {
  391. prefixHeader = HEADER_PREFIX_OBS
  392. }
  393. ok = ok || strings.HasPrefix(lowerKey, prefixHeader)
  394. if ok {
  395. if i == 0 {
  396. canonicalizedURL += "?"
  397. } else {
  398. canonicalizedURL += "&"
  399. }
  400. canonicalizedURL += getQueryURL(_key, _value)
  401. i++
  402. }
  403. }
  404. }
  405. return
  406. }
  407. func getQueryURL(key, value string) string {
  408. queryURL := ""
  409. queryURL += key
  410. queryURL += value
  411. return queryURL
  412. }