api.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984
  1. /*
  2. * MinIO Go Library for Amazon S3 Compatible Cloud Storage
  3. * Copyright 2015-2018 MinIO, Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package minio
  18. import (
  19. "bytes"
  20. "context"
  21. "errors"
  22. "fmt"
  23. "io"
  24. "io/ioutil"
  25. "math/rand"
  26. "net"
  27. "net/http"
  28. "net/http/cookiejar"
  29. "net/http/httputil"
  30. "net/url"
  31. "os"
  32. "runtime"
  33. "strings"
  34. "sync"
  35. "sync/atomic"
  36. "time"
  37. md5simd "github.com/minio/md5-simd"
  38. "github.com/minio/minio-go/v7/pkg/credentials"
  39. "github.com/minio/minio-go/v7/pkg/s3utils"
  40. "github.com/minio/minio-go/v7/pkg/signer"
  41. "golang.org/x/net/publicsuffix"
  42. )
  43. // Client implements Amazon S3 compatible methods.
  44. type Client struct {
  45. /// Standard options.
  46. // Parsed endpoint url provided by the user.
  47. endpointURL *url.URL
  48. // Holds various credential providers.
  49. credsProvider *credentials.Credentials
  50. // Custom signerType value overrides all credentials.
  51. overrideSignerType credentials.SignatureType
  52. // User supplied.
  53. appInfo struct {
  54. appName string
  55. appVersion string
  56. }
  57. // Indicate whether we are using https or not
  58. secure bool
  59. // Needs allocation.
  60. httpClient *http.Client
  61. bucketLocCache *bucketLocationCache
  62. // Advanced functionality.
  63. isTraceEnabled bool
  64. traceErrorsOnly bool
  65. traceOutput io.Writer
  66. // S3 specific accelerated endpoint.
  67. s3AccelerateEndpoint string
  68. // Region endpoint
  69. region string
  70. // Random seed.
  71. random *rand.Rand
  72. // lookup indicates type of url lookup supported by server. If not specified,
  73. // default to Auto.
  74. lookup BucketLookupType
  75. // Factory for MD5 hash functions.
  76. md5Hasher func() md5simd.Hasher
  77. sha256Hasher func() md5simd.Hasher
  78. healthCheckCh chan struct{}
  79. healthCheck int32
  80. lastOnline time.Time
  81. }
  82. // Options for New method
  83. type Options struct {
  84. Creds *credentials.Credentials
  85. Secure bool
  86. Transport http.RoundTripper
  87. Region string
  88. BucketLookup BucketLookupType
  89. // Custom hash routines. Leave nil to use standard.
  90. CustomMD5 func() md5simd.Hasher
  91. CustomSHA256 func() md5simd.Hasher
  92. }
  93. // Global constants.
  94. const (
  95. libraryName = "minio-go"
  96. libraryVersion = "v7.0.14"
  97. )
  98. // User Agent should always following the below style.
  99. // Please open an issue to discuss any new changes here.
  100. //
  101. // MinIO (OS; ARCH) LIB/VER APP/VER
  102. const (
  103. libraryUserAgentPrefix = "MinIO (" + runtime.GOOS + "; " + runtime.GOARCH + ") "
  104. libraryUserAgent = libraryUserAgentPrefix + libraryName + "/" + libraryVersion
  105. )
  106. // BucketLookupType is type of url lookup supported by server.
  107. type BucketLookupType int
  108. // Different types of url lookup supported by the server.Initialized to BucketLookupAuto
  109. const (
  110. BucketLookupAuto BucketLookupType = iota
  111. BucketLookupDNS
  112. BucketLookupPath
  113. )
  114. // New - instantiate minio client with options
  115. func New(endpoint string, opts *Options) (*Client, error) {
  116. if opts == nil {
  117. return nil, errors.New("no options provided")
  118. }
  119. clnt, err := privateNew(endpoint, opts)
  120. if err != nil {
  121. return nil, err
  122. }
  123. // Google cloud storage should be set to signature V2, force it if not.
  124. if s3utils.IsGoogleEndpoint(*clnt.endpointURL) {
  125. clnt.overrideSignerType = credentials.SignatureV2
  126. }
  127. // If Amazon S3 set to signature v4.
  128. if s3utils.IsAmazonEndpoint(*clnt.endpointURL) {
  129. clnt.overrideSignerType = credentials.SignatureV4
  130. }
  131. return clnt, nil
  132. }
  133. // EndpointURL returns the URL of the S3 endpoint.
  134. func (c *Client) EndpointURL() *url.URL {
  135. endpoint := *c.endpointURL // copy to prevent callers from modifying internal state
  136. return &endpoint
  137. }
  138. // lockedRandSource provides protected rand source, implements rand.Source interface.
  139. type lockedRandSource struct {
  140. lk sync.Mutex
  141. src rand.Source
  142. }
  143. // Int63 returns a non-negative pseudo-random 63-bit integer as an int64.
  144. func (r *lockedRandSource) Int63() (n int64) {
  145. r.lk.Lock()
  146. n = r.src.Int63()
  147. r.lk.Unlock()
  148. return
  149. }
  150. // Seed uses the provided seed value to initialize the generator to a
  151. // deterministic state.
  152. func (r *lockedRandSource) Seed(seed int64) {
  153. r.lk.Lock()
  154. r.src.Seed(seed)
  155. r.lk.Unlock()
  156. }
  157. // Redirect requests by re signing the request.
  158. func (c *Client) redirectHeaders(req *http.Request, via []*http.Request) error {
  159. if len(via) >= 5 {
  160. return errors.New("stopped after 5 redirects")
  161. }
  162. if len(via) == 0 {
  163. return nil
  164. }
  165. lastRequest := via[len(via)-1]
  166. var reAuth bool
  167. for attr, val := range lastRequest.Header {
  168. // if hosts do not match do not copy Authorization header
  169. if attr == "Authorization" && req.Host != lastRequest.Host {
  170. reAuth = true
  171. continue
  172. }
  173. if _, ok := req.Header[attr]; !ok {
  174. req.Header[attr] = val
  175. }
  176. }
  177. *c.endpointURL = *req.URL
  178. value, err := c.credsProvider.Get()
  179. if err != nil {
  180. return err
  181. }
  182. var (
  183. signerType = value.SignerType
  184. accessKeyID = value.AccessKeyID
  185. secretAccessKey = value.SecretAccessKey
  186. sessionToken = value.SessionToken
  187. region = c.region
  188. )
  189. // Custom signer set then override the behavior.
  190. if c.overrideSignerType != credentials.SignatureDefault {
  191. signerType = c.overrideSignerType
  192. }
  193. // If signerType returned by credentials helper is anonymous,
  194. // then do not sign regardless of signerType override.
  195. if value.SignerType == credentials.SignatureAnonymous {
  196. signerType = credentials.SignatureAnonymous
  197. }
  198. if reAuth {
  199. // Check if there is no region override, if not get it from the URL if possible.
  200. if region == "" {
  201. region = s3utils.GetRegionFromURL(*c.endpointURL)
  202. }
  203. switch {
  204. case signerType.IsV2():
  205. return errors.New("signature V2 cannot support redirection")
  206. case signerType.IsV4():
  207. signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, getDefaultLocation(*c.endpointURL, region))
  208. }
  209. }
  210. return nil
  211. }
  212. func privateNew(endpoint string, opts *Options) (*Client, error) {
  213. // construct endpoint.
  214. endpointURL, err := getEndpointURL(endpoint, opts.Secure)
  215. if err != nil {
  216. return nil, err
  217. }
  218. // Initialize cookies to preserve server sent cookies if any and replay
  219. // them upon each request.
  220. jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
  221. if err != nil {
  222. return nil, err
  223. }
  224. // instantiate new Client.
  225. clnt := new(Client)
  226. // Save the credentials.
  227. clnt.credsProvider = opts.Creds
  228. // Remember whether we are using https or not
  229. clnt.secure = opts.Secure
  230. // Save endpoint URL, user agent for future uses.
  231. clnt.endpointURL = endpointURL
  232. transport := opts.Transport
  233. if transport == nil {
  234. transport, err = DefaultTransport(opts.Secure)
  235. if err != nil {
  236. return nil, err
  237. }
  238. }
  239. // Instantiate http client and bucket location cache.
  240. clnt.httpClient = &http.Client{
  241. Jar: jar,
  242. Transport: transport,
  243. CheckRedirect: clnt.redirectHeaders,
  244. }
  245. // Sets custom region, if region is empty bucket location cache is used automatically.
  246. if opts.Region == "" {
  247. opts.Region = s3utils.GetRegionFromURL(*clnt.endpointURL)
  248. }
  249. clnt.region = opts.Region
  250. // Instantiate bucket location cache.
  251. clnt.bucketLocCache = newBucketLocationCache()
  252. // Introduce a new locked random seed.
  253. clnt.random = rand.New(&lockedRandSource{src: rand.NewSource(time.Now().UTC().UnixNano())})
  254. // Add default md5 hasher.
  255. clnt.md5Hasher = opts.CustomMD5
  256. clnt.sha256Hasher = opts.CustomSHA256
  257. if clnt.md5Hasher == nil {
  258. clnt.md5Hasher = newMd5Hasher
  259. }
  260. if clnt.sha256Hasher == nil {
  261. clnt.sha256Hasher = newSHA256Hasher
  262. }
  263. // Sets bucket lookup style, whether server accepts DNS or Path lookup. Default is Auto - determined
  264. // by the SDK. When Auto is specified, DNS lookup is used for Amazon/Google cloud endpoints and Path for all other endpoints.
  265. clnt.lookup = opts.BucketLookup
  266. // healthcheck is not initialized
  267. clnt.healthCheck = unknown
  268. // Return.
  269. return clnt, nil
  270. }
  271. // SetAppInfo - add application details to user agent.
  272. func (c *Client) SetAppInfo(appName string, appVersion string) {
  273. // if app name and version not set, we do not set a new user agent.
  274. if appName != "" && appVersion != "" {
  275. c.appInfo.appName = appName
  276. c.appInfo.appVersion = appVersion
  277. }
  278. }
  279. // TraceOn - enable HTTP tracing.
  280. func (c *Client) TraceOn(outputStream io.Writer) {
  281. // if outputStream is nil then default to os.Stdout.
  282. if outputStream == nil {
  283. outputStream = os.Stdout
  284. }
  285. // Sets a new output stream.
  286. c.traceOutput = outputStream
  287. // Enable tracing.
  288. c.isTraceEnabled = true
  289. }
  290. // TraceErrorsOnlyOn - same as TraceOn, but only errors will be traced.
  291. func (c *Client) TraceErrorsOnlyOn(outputStream io.Writer) {
  292. c.TraceOn(outputStream)
  293. c.traceErrorsOnly = true
  294. }
  295. // TraceErrorsOnlyOff - Turns off the errors only tracing and everything will be traced after this call.
  296. // If all tracing needs to be turned off, call TraceOff().
  297. func (c *Client) TraceErrorsOnlyOff() {
  298. c.traceErrorsOnly = false
  299. }
  300. // TraceOff - disable HTTP tracing.
  301. func (c *Client) TraceOff() {
  302. // Disable tracing.
  303. c.isTraceEnabled = false
  304. c.traceErrorsOnly = false
  305. }
  306. // SetS3TransferAccelerate - turns s3 accelerated endpoint on or off for all your
  307. // requests. This feature is only specific to S3 for all other endpoints this
  308. // function does nothing. To read further details on s3 transfer acceleration
  309. // please vist -
  310. // http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
  311. func (c *Client) SetS3TransferAccelerate(accelerateEndpoint string) {
  312. if s3utils.IsAmazonEndpoint(*c.endpointURL) {
  313. c.s3AccelerateEndpoint = accelerateEndpoint
  314. }
  315. }
  316. // Hash materials provides relevant initialized hash algo writers
  317. // based on the expected signature type.
  318. //
  319. // - For signature v4 request if the connection is insecure compute only sha256.
  320. // - For signature v4 request if the connection is secure compute only md5.
  321. // - For anonymous request compute md5.
  322. func (c *Client) hashMaterials(isMd5Requested bool) (hashAlgos map[string]md5simd.Hasher, hashSums map[string][]byte) {
  323. hashSums = make(map[string][]byte)
  324. hashAlgos = make(map[string]md5simd.Hasher)
  325. if c.overrideSignerType.IsV4() {
  326. if c.secure {
  327. hashAlgos["md5"] = c.md5Hasher()
  328. } else {
  329. hashAlgos["sha256"] = c.sha256Hasher()
  330. }
  331. } else {
  332. if c.overrideSignerType.IsAnonymous() {
  333. hashAlgos["md5"] = c.md5Hasher()
  334. }
  335. }
  336. if isMd5Requested {
  337. hashAlgos["md5"] = c.md5Hasher()
  338. }
  339. return hashAlgos, hashSums
  340. }
  341. const (
  342. unknown = -1
  343. offline = 0
  344. online = 1
  345. )
  346. // IsOnline returns true if healthcheck enabled and client is online
  347. func (c *Client) IsOnline() bool {
  348. switch atomic.LoadInt32(&c.healthCheck) {
  349. case online, unknown:
  350. return true
  351. }
  352. return false
  353. }
  354. // IsOffline returns true if healthcheck enabled and client is offline
  355. func (c *Client) IsOffline() bool {
  356. return !c.IsOnline()
  357. }
  358. // HealthCheck starts a healthcheck to see if endpoint is up. Returns a context cancellation function
  359. // and and error if health check is already started
  360. func (c *Client) HealthCheck(hcDuration time.Duration) (context.CancelFunc, error) {
  361. if atomic.LoadInt32(&c.healthCheck) == online {
  362. return nil, fmt.Errorf("health check running already")
  363. }
  364. if hcDuration < 1*time.Second {
  365. return nil, fmt.Errorf("health check duration should be atleast 1 second")
  366. }
  367. ctx, cancelFn := context.WithCancel(context.Background())
  368. c.healthCheckCh = make(chan struct{})
  369. atomic.StoreInt32(&c.healthCheck, online)
  370. probeBucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "probe-health-")
  371. go func(duration time.Duration) {
  372. timer := time.NewTimer(duration)
  373. defer timer.Stop()
  374. for {
  375. select {
  376. case <-ctx.Done():
  377. close(c.healthCheckCh)
  378. atomic.StoreInt32(&c.healthCheck, unknown)
  379. return
  380. case <-timer.C:
  381. timer.Reset(duration)
  382. // Do health check the first time and ONLY if the connection is marked offline
  383. if c.IsOffline() || c.lastOnline.IsZero() {
  384. _, err := c.getBucketLocation(context.Background(), probeBucketName)
  385. if err != nil && IsNetworkOrHostDown(err, false) {
  386. atomic.StoreInt32(&c.healthCheck, offline)
  387. }
  388. switch ToErrorResponse(err).Code {
  389. case "NoSuchBucket", "AccessDenied", "":
  390. c.lastOnline = time.Now()
  391. atomic.StoreInt32(&c.healthCheck, online)
  392. }
  393. }
  394. case <-c.healthCheckCh:
  395. // set offline if client saw a network error
  396. atomic.StoreInt32(&c.healthCheck, offline)
  397. }
  398. }
  399. }(hcDuration)
  400. return cancelFn, nil
  401. }
  402. // requestMetadata - is container for all the values to make a request.
  403. type requestMetadata struct {
  404. // If set newRequest presigns the URL.
  405. presignURL bool
  406. // User supplied.
  407. bucketName string
  408. objectName string
  409. queryValues url.Values
  410. customHeader http.Header
  411. expires int64
  412. // Generated by our internal code.
  413. bucketLocation string
  414. contentBody io.Reader
  415. contentLength int64
  416. contentMD5Base64 string // carries base64 encoded md5sum
  417. contentSHA256Hex string // carries hex encoded sha256sum
  418. }
  419. // dumpHTTP - dump HTTP request and response.
  420. func (c Client) dumpHTTP(req *http.Request, resp *http.Response) error {
  421. // Starts http dump.
  422. _, err := fmt.Fprintln(c.traceOutput, "---------START-HTTP---------")
  423. if err != nil {
  424. return err
  425. }
  426. // Filter out Signature field from Authorization header.
  427. origAuth := req.Header.Get("Authorization")
  428. if origAuth != "" {
  429. req.Header.Set("Authorization", redactSignature(origAuth))
  430. }
  431. // Only display request header.
  432. reqTrace, err := httputil.DumpRequestOut(req, false)
  433. if err != nil {
  434. return err
  435. }
  436. // Write request to trace output.
  437. _, err = fmt.Fprint(c.traceOutput, string(reqTrace))
  438. if err != nil {
  439. return err
  440. }
  441. // Only display response header.
  442. var respTrace []byte
  443. // For errors we make sure to dump response body as well.
  444. if resp.StatusCode != http.StatusOK &&
  445. resp.StatusCode != http.StatusPartialContent &&
  446. resp.StatusCode != http.StatusNoContent {
  447. respTrace, err = httputil.DumpResponse(resp, true)
  448. if err != nil {
  449. return err
  450. }
  451. } else {
  452. respTrace, err = httputil.DumpResponse(resp, false)
  453. if err != nil {
  454. return err
  455. }
  456. }
  457. // Write response to trace output.
  458. _, err = fmt.Fprint(c.traceOutput, strings.TrimSuffix(string(respTrace), "\r\n"))
  459. if err != nil {
  460. return err
  461. }
  462. // Ends the http dump.
  463. _, err = fmt.Fprintln(c.traceOutput, "---------END-HTTP---------")
  464. if err != nil {
  465. return err
  466. }
  467. // Returns success.
  468. return nil
  469. }
  470. // do - execute http request.
  471. func (c Client) do(req *http.Request) (*http.Response, error) {
  472. resp, err := c.httpClient.Do(req)
  473. if err != nil {
  474. // Handle this specifically for now until future Golang versions fix this issue properly.
  475. if urlErr, ok := err.(*url.Error); ok {
  476. if strings.Contains(urlErr.Err.Error(), "EOF") {
  477. return nil, &url.Error{
  478. Op: urlErr.Op,
  479. URL: urlErr.URL,
  480. Err: errors.New("Connection closed by foreign host " + urlErr.URL + ". Retry again."),
  481. }
  482. }
  483. }
  484. return nil, err
  485. }
  486. // Response cannot be non-nil, report error if thats the case.
  487. if resp == nil {
  488. msg := "Response is empty. " + reportIssue
  489. return nil, errInvalidArgument(msg)
  490. }
  491. // If trace is enabled, dump http request and response,
  492. // except when the traceErrorsOnly enabled and the response's status code is ok
  493. if c.isTraceEnabled && !(c.traceErrorsOnly && resp.StatusCode == http.StatusOK) {
  494. err = c.dumpHTTP(req, resp)
  495. if err != nil {
  496. return nil, err
  497. }
  498. }
  499. return resp, nil
  500. }
  501. // List of success status.
  502. var successStatus = []int{
  503. http.StatusOK,
  504. http.StatusNoContent,
  505. http.StatusPartialContent,
  506. }
  507. // executeMethod - instantiates a given method, and retries the
  508. // request upon any error up to maxRetries attempts in a binomially
  509. // delayed manner using a standard back off algorithm.
  510. func (c Client) executeMethod(ctx context.Context, method string, metadata requestMetadata) (res *http.Response, err error) {
  511. var retryable bool // Indicates if request can be retried.
  512. var bodySeeker io.Seeker // Extracted seeker from io.Reader.
  513. var reqRetry = MaxRetry // Indicates how many times we can retry the request
  514. if metadata.contentBody != nil {
  515. // Check if body is seekable then it is retryable.
  516. bodySeeker, retryable = metadata.contentBody.(io.Seeker)
  517. switch bodySeeker {
  518. case os.Stdin, os.Stdout, os.Stderr:
  519. retryable = false
  520. }
  521. // Retry only when reader is seekable
  522. if !retryable {
  523. reqRetry = 1
  524. }
  525. // Figure out if the body can be closed - if yes
  526. // we will definitely close it upon the function
  527. // return.
  528. bodyCloser, ok := metadata.contentBody.(io.Closer)
  529. if ok {
  530. defer bodyCloser.Close()
  531. }
  532. }
  533. // Create cancel context to control 'newRetryTimer' go routine.
  534. retryCtx, cancel := context.WithCancel(ctx)
  535. // Indicate to our routine to exit cleanly upon return.
  536. defer cancel()
  537. for range c.newRetryTimer(retryCtx, reqRetry, DefaultRetryUnit, DefaultRetryCap, MaxJitter) {
  538. // Retry executes the following function body if request has an
  539. // error until maxRetries have been exhausted, retry attempts are
  540. // performed after waiting for a given period of time in a
  541. // binomial fashion.
  542. if retryable {
  543. // Seek back to beginning for each attempt.
  544. if _, err = bodySeeker.Seek(0, 0); err != nil {
  545. // If seek failed, no need to retry.
  546. return nil, err
  547. }
  548. }
  549. // Instantiate a new request.
  550. var req *http.Request
  551. req, err = c.newRequest(ctx, method, metadata)
  552. if err != nil {
  553. errResponse := ToErrorResponse(err)
  554. if isS3CodeRetryable(errResponse.Code) {
  555. continue // Retry.
  556. }
  557. if atomic.LoadInt32(&c.healthCheck) != unknown && IsNetworkOrHostDown(err, false) {
  558. select {
  559. case c.healthCheckCh <- struct{}{}:
  560. default:
  561. }
  562. }
  563. return nil, err
  564. }
  565. // Initiate the request.
  566. res, err = c.do(req)
  567. if err != nil {
  568. if atomic.LoadInt32(&c.healthCheck) != unknown && IsNetworkOrHostDown(err, false) {
  569. select {
  570. case c.healthCheckCh <- struct{}{}:
  571. default:
  572. }
  573. }
  574. if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
  575. return nil, err
  576. }
  577. // Retry the request
  578. continue
  579. }
  580. // For any known successful http status, return quickly.
  581. for _, httpStatus := range successStatus {
  582. if httpStatus == res.StatusCode {
  583. return res, nil
  584. }
  585. }
  586. // Read the body to be saved later.
  587. errBodyBytes, err := ioutil.ReadAll(res.Body)
  588. // res.Body should be closed
  589. closeResponse(res)
  590. if err != nil {
  591. return nil, err
  592. }
  593. // Save the body.
  594. errBodySeeker := bytes.NewReader(errBodyBytes)
  595. res.Body = ioutil.NopCloser(errBodySeeker)
  596. // For errors verify if its retryable otherwise fail quickly.
  597. errResponse := ToErrorResponse(httpRespToErrorResponse(res, metadata.bucketName, metadata.objectName))
  598. // Save the body back again.
  599. errBodySeeker.Seek(0, 0) // Seek back to starting point.
  600. res.Body = ioutil.NopCloser(errBodySeeker)
  601. // Bucket region if set in error response and the error
  602. // code dictates invalid region, we can retry the request
  603. // with the new region.
  604. //
  605. // Additionally we should only retry if bucketLocation and custom
  606. // region is empty.
  607. if c.region == "" {
  608. switch errResponse.Code {
  609. case "AuthorizationHeaderMalformed":
  610. fallthrough
  611. case "InvalidRegion":
  612. fallthrough
  613. case "AccessDenied":
  614. if errResponse.Region == "" {
  615. // Region is empty we simply return the error.
  616. return res, err
  617. }
  618. // Region is not empty figure out a way to
  619. // handle this appropriately.
  620. if metadata.bucketName != "" {
  621. // Gather Cached location only if bucketName is present.
  622. if location, cachedOk := c.bucketLocCache.Get(metadata.bucketName); cachedOk && location != errResponse.Region {
  623. c.bucketLocCache.Set(metadata.bucketName, errResponse.Region)
  624. continue // Retry.
  625. }
  626. } else {
  627. // This is for ListBuckets() fallback.
  628. if errResponse.Region != metadata.bucketLocation {
  629. // Retry if the error response has a different region
  630. // than the request we just made.
  631. metadata.bucketLocation = errResponse.Region
  632. continue // Retry
  633. }
  634. }
  635. }
  636. }
  637. // Verify if error response code is retryable.
  638. if isS3CodeRetryable(errResponse.Code) {
  639. continue // Retry.
  640. }
  641. // Verify if http status code is retryable.
  642. if isHTTPStatusRetryable(res.StatusCode) {
  643. continue // Retry.
  644. }
  645. // For all other cases break out of the retry loop.
  646. break
  647. }
  648. // Return an error when retry is canceled or deadlined
  649. if e := retryCtx.Err(); e != nil {
  650. return nil, e
  651. }
  652. return res, err
  653. }
  654. // newRequest - instantiate a new HTTP request for a given method.
  655. func (c Client) newRequest(ctx context.Context, method string, metadata requestMetadata) (req *http.Request, err error) {
  656. // If no method is supplied default to 'POST'.
  657. if method == "" {
  658. method = http.MethodPost
  659. }
  660. location := metadata.bucketLocation
  661. if location == "" {
  662. if metadata.bucketName != "" {
  663. // Gather location only if bucketName is present.
  664. location, err = c.getBucketLocation(ctx, metadata.bucketName)
  665. if err != nil {
  666. return nil, err
  667. }
  668. }
  669. if location == "" {
  670. location = getDefaultLocation(*c.endpointURL, c.region)
  671. }
  672. }
  673. // Look if target url supports virtual host.
  674. // We explicitly disallow MakeBucket calls to not use virtual DNS style,
  675. // since the resolution may fail.
  676. isMakeBucket := (metadata.objectName == "" && method == http.MethodPut && len(metadata.queryValues) == 0)
  677. isVirtualHost := c.isVirtualHostStyleRequest(*c.endpointURL, metadata.bucketName) && !isMakeBucket
  678. // Construct a new target URL.
  679. targetURL, err := c.makeTargetURL(metadata.bucketName, metadata.objectName, location,
  680. isVirtualHost, metadata.queryValues)
  681. if err != nil {
  682. return nil, err
  683. }
  684. // Initialize a new HTTP request for the method.
  685. req, err = http.NewRequestWithContext(ctx, method, targetURL.String(), nil)
  686. if err != nil {
  687. return nil, err
  688. }
  689. // Get credentials from the configured credentials provider.
  690. value, err := c.credsProvider.Get()
  691. if err != nil {
  692. return nil, err
  693. }
  694. var (
  695. signerType = value.SignerType
  696. accessKeyID = value.AccessKeyID
  697. secretAccessKey = value.SecretAccessKey
  698. sessionToken = value.SessionToken
  699. )
  700. // Custom signer set then override the behavior.
  701. if c.overrideSignerType != credentials.SignatureDefault {
  702. signerType = c.overrideSignerType
  703. }
  704. // If signerType returned by credentials helper is anonymous,
  705. // then do not sign regardless of signerType override.
  706. if value.SignerType == credentials.SignatureAnonymous {
  707. signerType = credentials.SignatureAnonymous
  708. }
  709. // Generate presign url if needed, return right here.
  710. if metadata.expires != 0 && metadata.presignURL {
  711. if signerType.IsAnonymous() {
  712. return nil, errInvalidArgument("Presigned URLs cannot be generated with anonymous credentials.")
  713. }
  714. if signerType.IsV2() {
  715. // Presign URL with signature v2.
  716. req = signer.PreSignV2(*req, accessKeyID, secretAccessKey, metadata.expires, isVirtualHost)
  717. } else if signerType.IsV4() {
  718. // Presign URL with signature v4.
  719. req = signer.PreSignV4(*req, accessKeyID, secretAccessKey, sessionToken, location, metadata.expires)
  720. }
  721. return req, nil
  722. }
  723. // Set 'User-Agent' header for the request.
  724. c.setUserAgent(req)
  725. // Set all headers.
  726. for k, v := range metadata.customHeader {
  727. req.Header.Set(k, v[0])
  728. }
  729. // Go net/http notoriously closes the request body.
  730. // - The request Body, if non-nil, will be closed by the underlying Transport, even on errors.
  731. // This can cause underlying *os.File seekers to fail, avoid that
  732. // by making sure to wrap the closer as a nop.
  733. if metadata.contentLength == 0 {
  734. req.Body = nil
  735. } else {
  736. req.Body = ioutil.NopCloser(metadata.contentBody)
  737. }
  738. // Set incoming content-length.
  739. req.ContentLength = metadata.contentLength
  740. if req.ContentLength <= -1 {
  741. // For unknown content length, we upload using transfer-encoding: chunked.
  742. req.TransferEncoding = []string{"chunked"}
  743. }
  744. // set md5Sum for content protection.
  745. if len(metadata.contentMD5Base64) > 0 {
  746. req.Header.Set("Content-Md5", metadata.contentMD5Base64)
  747. }
  748. // For anonymous requests just return.
  749. if signerType.IsAnonymous() {
  750. return req, nil
  751. }
  752. switch {
  753. case signerType.IsV2():
  754. // Add signature version '2' authorization header.
  755. req = signer.SignV2(*req, accessKeyID, secretAccessKey, isVirtualHost)
  756. case metadata.objectName != "" && metadata.queryValues == nil && method == http.MethodPut && metadata.customHeader.Get("X-Amz-Copy-Source") == "" && !c.secure:
  757. // Streaming signature is used by default for a PUT object request. Additionally we also
  758. // look if the initialized client is secure, if yes then we don't need to perform
  759. // streaming signature.
  760. req = signer.StreamingSignV4(req, accessKeyID,
  761. secretAccessKey, sessionToken, location, metadata.contentLength, time.Now().UTC())
  762. default:
  763. // Set sha256 sum for signature calculation only with signature version '4'.
  764. shaHeader := unsignedPayload
  765. if metadata.contentSHA256Hex != "" {
  766. shaHeader = metadata.contentSHA256Hex
  767. }
  768. req.Header.Set("X-Amz-Content-Sha256", shaHeader)
  769. // Add signature version '4' authorization header.
  770. req = signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, location)
  771. }
  772. // Return request.
  773. return req, nil
  774. }
  775. // set User agent.
  776. func (c Client) setUserAgent(req *http.Request) {
  777. req.Header.Set("User-Agent", libraryUserAgent)
  778. if c.appInfo.appName != "" && c.appInfo.appVersion != "" {
  779. req.Header.Set("User-Agent", libraryUserAgent+" "+c.appInfo.appName+"/"+c.appInfo.appVersion)
  780. }
  781. }
  782. // makeTargetURL make a new target url.
  783. func (c Client) makeTargetURL(bucketName, objectName, bucketLocation string, isVirtualHostStyle bool, queryValues url.Values) (*url.URL, error) {
  784. host := c.endpointURL.Host
  785. // For Amazon S3 endpoint, try to fetch location based endpoint.
  786. if s3utils.IsAmazonEndpoint(*c.endpointURL) {
  787. if c.s3AccelerateEndpoint != "" && bucketName != "" {
  788. // http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
  789. // Disable transfer acceleration for non-compliant bucket names.
  790. if strings.Contains(bucketName, ".") {
  791. return nil, errTransferAccelerationBucket(bucketName)
  792. }
  793. // If transfer acceleration is requested set new host.
  794. // For more details about enabling transfer acceleration read here.
  795. // http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
  796. host = c.s3AccelerateEndpoint
  797. } else {
  798. // Do not change the host if the endpoint URL is a FIPS S3 endpoint.
  799. if !s3utils.IsAmazonFIPSEndpoint(*c.endpointURL) {
  800. // Fetch new host based on the bucket location.
  801. host = getS3Endpoint(bucketLocation)
  802. }
  803. }
  804. }
  805. // Save scheme.
  806. scheme := c.endpointURL.Scheme
  807. // Strip port 80 and 443 so we won't send these ports in Host header.
  808. // The reason is that browsers and curl automatically remove :80 and :443
  809. // with the generated presigned urls, then a signature mismatch error.
  810. if h, p, err := net.SplitHostPort(host); err == nil {
  811. if scheme == "http" && p == "80" || scheme == "https" && p == "443" {
  812. host = h
  813. }
  814. }
  815. urlStr := scheme + "://" + host + "/"
  816. // Make URL only if bucketName is available, otherwise use the
  817. // endpoint URL.
  818. if bucketName != "" {
  819. // If endpoint supports virtual host style use that always.
  820. // Currently only S3 and Google Cloud Storage would support
  821. // virtual host style.
  822. if isVirtualHostStyle {
  823. urlStr = scheme + "://" + bucketName + "." + host + "/"
  824. if objectName != "" {
  825. urlStr = urlStr + s3utils.EncodePath(objectName)
  826. }
  827. } else {
  828. // If not fall back to using path style.
  829. urlStr = urlStr + bucketName + "/"
  830. if objectName != "" {
  831. urlStr = urlStr + s3utils.EncodePath(objectName)
  832. }
  833. }
  834. }
  835. // If there are any query values, add them to the end.
  836. if len(queryValues) > 0 {
  837. urlStr = urlStr + "?" + s3utils.QueryEncode(queryValues)
  838. }
  839. return url.Parse(urlStr)
  840. }
  841. // returns true if virtual hosted style requests are to be used.
  842. func (c *Client) isVirtualHostStyleRequest(url url.URL, bucketName string) bool {
  843. if bucketName == "" {
  844. return false
  845. }
  846. if c.lookup == BucketLookupDNS {
  847. return true
  848. }
  849. if c.lookup == BucketLookupPath {
  850. return false
  851. }
  852. // default to virtual only for Amazon/Google storage. In all other cases use
  853. // path style requests
  854. return s3utils.IsVirtualHostSupported(url, bucketName)
  855. }