api.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  1. /*
  2. * Minio Go Library for Amazon S3 Compatible Cloud Storage
  3. * Copyright 2015-2017 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. "crypto/md5"
  22. "crypto/sha256"
  23. "errors"
  24. "fmt"
  25. "hash"
  26. "io"
  27. "io/ioutil"
  28. "math/rand"
  29. "net"
  30. "net/http"
  31. "net/http/httputil"
  32. "net/url"
  33. "os"
  34. "runtime"
  35. "strings"
  36. "sync"
  37. "time"
  38. "github.com/minio/minio-go/pkg/credentials"
  39. "github.com/minio/minio-go/pkg/s3signer"
  40. "github.com/minio/minio-go/pkg/s3utils"
  41. )
  42. // Client implements Amazon S3 compatible methods.
  43. type Client struct {
  44. /// Standard options.
  45. // Parsed endpoint url provided by the user.
  46. endpointURL url.URL
  47. // Holds various credential providers.
  48. credsProvider *credentials.Credentials
  49. // Custom signerType value overrides all credentials.
  50. overrideSignerType credentials.SignatureType
  51. // User supplied.
  52. appInfo struct {
  53. appName string
  54. appVersion string
  55. }
  56. // Indicate whether we are using https or not
  57. secure bool
  58. // Needs allocation.
  59. httpClient *http.Client
  60. bucketLocCache *bucketLocationCache
  61. // Advanced functionality.
  62. isTraceEnabled bool
  63. traceOutput io.Writer
  64. // S3 specific accelerated endpoint.
  65. s3AccelerateEndpoint string
  66. // Region endpoint
  67. region string
  68. // Random seed.
  69. random *rand.Rand
  70. }
  71. // Global constants.
  72. const (
  73. libraryName = "minio-go"
  74. libraryVersion = "4.0.6"
  75. )
  76. // User Agent should always following the below style.
  77. // Please open an issue to discuss any new changes here.
  78. //
  79. // Minio (OS; ARCH) LIB/VER APP/VER
  80. const (
  81. libraryUserAgentPrefix = "Minio (" + runtime.GOOS + "; " + runtime.GOARCH + ") "
  82. libraryUserAgent = libraryUserAgentPrefix + libraryName + "/" + libraryVersion
  83. )
  84. // NewV2 - instantiate minio client with Amazon S3 signature version
  85. // '2' compatibility.
  86. func NewV2(endpoint string, accessKeyID, secretAccessKey string, secure bool) (*Client, error) {
  87. creds := credentials.NewStaticV2(accessKeyID, secretAccessKey, "")
  88. clnt, err := privateNew(endpoint, creds, secure, "")
  89. if err != nil {
  90. return nil, err
  91. }
  92. clnt.overrideSignerType = credentials.SignatureV2
  93. return clnt, nil
  94. }
  95. // NewV4 - instantiate minio client with Amazon S3 signature version
  96. // '4' compatibility.
  97. func NewV4(endpoint string, accessKeyID, secretAccessKey string, secure bool) (*Client, error) {
  98. creds := credentials.NewStaticV4(accessKeyID, secretAccessKey, "")
  99. clnt, err := privateNew(endpoint, creds, secure, "")
  100. if err != nil {
  101. return nil, err
  102. }
  103. clnt.overrideSignerType = credentials.SignatureV4
  104. return clnt, nil
  105. }
  106. // New - instantiate minio client, adds automatic verification of signature.
  107. func New(endpoint, accessKeyID, secretAccessKey string, secure bool) (*Client, error) {
  108. creds := credentials.NewStaticV4(accessKeyID, secretAccessKey, "")
  109. clnt, err := privateNew(endpoint, creds, secure, "")
  110. if err != nil {
  111. return nil, err
  112. }
  113. // Google cloud storage should be set to signature V2, force it if not.
  114. if s3utils.IsGoogleEndpoint(clnt.endpointURL) {
  115. clnt.overrideSignerType = credentials.SignatureV2
  116. }
  117. // If Amazon S3 set to signature v4.
  118. if s3utils.IsAmazonEndpoint(clnt.endpointURL) {
  119. clnt.overrideSignerType = credentials.SignatureV4
  120. }
  121. return clnt, nil
  122. }
  123. // NewWithCredentials - instantiate minio client with credentials provider
  124. // for retrieving credentials from various credentials provider such as
  125. // IAM, File, Env etc.
  126. func NewWithCredentials(endpoint string, creds *credentials.Credentials, secure bool, region string) (*Client, error) {
  127. return privateNew(endpoint, creds, secure, region)
  128. }
  129. // NewWithRegion - instantiate minio client, with region configured. Unlike New(),
  130. // NewWithRegion avoids bucket-location lookup operations and it is slightly faster.
  131. // Use this function when if your application deals with single region.
  132. func NewWithRegion(endpoint, accessKeyID, secretAccessKey string, secure bool, region string) (*Client, error) {
  133. creds := credentials.NewStaticV4(accessKeyID, secretAccessKey, "")
  134. return privateNew(endpoint, creds, secure, region)
  135. }
  136. // lockedRandSource provides protected rand source, implements rand.Source interface.
  137. type lockedRandSource struct {
  138. lk sync.Mutex
  139. src rand.Source
  140. }
  141. // Int63 returns a non-negative pseudo-random 63-bit integer as an int64.
  142. func (r *lockedRandSource) Int63() (n int64) {
  143. r.lk.Lock()
  144. n = r.src.Int63()
  145. r.lk.Unlock()
  146. return
  147. }
  148. // Seed uses the provided seed value to initialize the generator to a
  149. // deterministic state.
  150. func (r *lockedRandSource) Seed(seed int64) {
  151. r.lk.Lock()
  152. r.src.Seed(seed)
  153. r.lk.Unlock()
  154. }
  155. // getRegionFromURL - parse region from URL if present.
  156. func getRegionFromURL(u url.URL) (region string) {
  157. region = ""
  158. if s3utils.IsGoogleEndpoint(u) {
  159. return
  160. } else if s3utils.IsAmazonChinaEndpoint(u) {
  161. // For china specifically we need to set everything to
  162. // cn-north-1 for now, there is no easier way until AWS S3
  163. // provides a cleaner compatible API across "us-east-1" and
  164. // China region.
  165. return "cn-north-1"
  166. } else if s3utils.IsAmazonGovCloudEndpoint(u) {
  167. // For us-gov specifically we need to set everything to
  168. // us-gov-west-1 for now, there is no easier way until AWS S3
  169. // provides a cleaner compatible API across "us-east-1" and
  170. // Gov cloud region.
  171. return "us-gov-west-1"
  172. }
  173. parts := s3utils.AmazonS3Host.FindStringSubmatch(u.Host)
  174. if len(parts) > 1 {
  175. region = parts[1]
  176. }
  177. return region
  178. }
  179. func privateNew(endpoint string, creds *credentials.Credentials, secure bool, region string) (*Client, error) {
  180. // construct endpoint.
  181. endpointURL, err := getEndpointURL(endpoint, secure)
  182. if err != nil {
  183. return nil, err
  184. }
  185. // instantiate new Client.
  186. clnt := new(Client)
  187. // Save the credentials.
  188. clnt.credsProvider = creds
  189. // Remember whether we are using https or not
  190. clnt.secure = secure
  191. // Save endpoint URL, user agent for future uses.
  192. clnt.endpointURL = *endpointURL
  193. // Instantiate http client and bucket location cache.
  194. clnt.httpClient = &http.Client{
  195. Transport: defaultMinioTransport,
  196. }
  197. // Sets custom region, if region is empty bucket location cache is used automatically.
  198. if region == "" {
  199. region = getRegionFromURL(clnt.endpointURL)
  200. }
  201. clnt.region = region
  202. // Instantiate bucket location cache.
  203. clnt.bucketLocCache = newBucketLocationCache()
  204. // Introduce a new locked random seed.
  205. clnt.random = rand.New(&lockedRandSource{src: rand.NewSource(time.Now().UTC().UnixNano())})
  206. // Return.
  207. return clnt, nil
  208. }
  209. // SetAppInfo - add application details to user agent.
  210. func (c *Client) SetAppInfo(appName string, appVersion string) {
  211. // if app name and version not set, we do not set a new user agent.
  212. if appName != "" && appVersion != "" {
  213. c.appInfo = struct {
  214. appName string
  215. appVersion string
  216. }{}
  217. c.appInfo.appName = appName
  218. c.appInfo.appVersion = appVersion
  219. }
  220. }
  221. // SetCustomTransport - set new custom transport.
  222. func (c *Client) SetCustomTransport(customHTTPTransport http.RoundTripper) {
  223. // Set this to override default transport
  224. // ``http.DefaultTransport``.
  225. //
  226. // This transport is usually needed for debugging OR to add your
  227. // own custom TLS certificates on the client transport, for custom
  228. // CA's and certs which are not part of standard certificate
  229. // authority follow this example :-
  230. //
  231. // tr := &http.Transport{
  232. // TLSClientConfig: &tls.Config{RootCAs: pool},
  233. // DisableCompression: true,
  234. // }
  235. // api.SetTransport(tr)
  236. //
  237. if c.httpClient != nil {
  238. c.httpClient.Transport = customHTTPTransport
  239. }
  240. }
  241. // TraceOn - enable HTTP tracing.
  242. func (c *Client) TraceOn(outputStream io.Writer) {
  243. // if outputStream is nil then default to os.Stdout.
  244. if outputStream == nil {
  245. outputStream = os.Stdout
  246. }
  247. // Sets a new output stream.
  248. c.traceOutput = outputStream
  249. // Enable tracing.
  250. c.isTraceEnabled = true
  251. }
  252. // TraceOff - disable HTTP tracing.
  253. func (c *Client) TraceOff() {
  254. // Disable tracing.
  255. c.isTraceEnabled = false
  256. }
  257. // SetS3TransferAccelerate - turns s3 accelerated endpoint on or off for all your
  258. // requests. This feature is only specific to S3 for all other endpoints this
  259. // function does nothing. To read further details on s3 transfer acceleration
  260. // please vist -
  261. // http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
  262. func (c *Client) SetS3TransferAccelerate(accelerateEndpoint string) {
  263. if s3utils.IsAmazonEndpoint(c.endpointURL) {
  264. c.s3AccelerateEndpoint = accelerateEndpoint
  265. }
  266. }
  267. // Hash materials provides relevant initialized hash algo writers
  268. // based on the expected signature type.
  269. //
  270. // - For signature v4 request if the connection is insecure compute only sha256.
  271. // - For signature v4 request if the connection is secure compute only md5.
  272. // - For anonymous request compute md5.
  273. func (c *Client) hashMaterials() (hashAlgos map[string]hash.Hash, hashSums map[string][]byte) {
  274. hashSums = make(map[string][]byte)
  275. hashAlgos = make(map[string]hash.Hash)
  276. if c.overrideSignerType.IsV4() {
  277. if c.secure {
  278. hashAlgos["md5"] = md5.New()
  279. } else {
  280. hashAlgos["sha256"] = sha256.New()
  281. }
  282. } else {
  283. if c.overrideSignerType.IsAnonymous() {
  284. hashAlgos["md5"] = md5.New()
  285. }
  286. }
  287. return hashAlgos, hashSums
  288. }
  289. // requestMetadata - is container for all the values to make a request.
  290. type requestMetadata struct {
  291. // If set newRequest presigns the URL.
  292. presignURL bool
  293. // User supplied.
  294. bucketName string
  295. objectName string
  296. queryValues url.Values
  297. customHeader http.Header
  298. expires int64
  299. // Generated by our internal code.
  300. bucketLocation string
  301. contentBody io.Reader
  302. contentLength int64
  303. contentMD5Base64 string // carries base64 encoded md5sum
  304. contentSHA256Hex string // carries hex encoded sha256sum
  305. }
  306. // dumpHTTP - dump HTTP request and response.
  307. func (c Client) dumpHTTP(req *http.Request, resp *http.Response) error {
  308. // Starts http dump.
  309. _, err := fmt.Fprintln(c.traceOutput, "---------START-HTTP---------")
  310. if err != nil {
  311. return err
  312. }
  313. // Filter out Signature field from Authorization header.
  314. origAuth := req.Header.Get("Authorization")
  315. if origAuth != "" {
  316. req.Header.Set("Authorization", redactSignature(origAuth))
  317. }
  318. // Only display request header.
  319. reqTrace, err := httputil.DumpRequestOut(req, false)
  320. if err != nil {
  321. return err
  322. }
  323. // Write request to trace output.
  324. _, err = fmt.Fprint(c.traceOutput, string(reqTrace))
  325. if err != nil {
  326. return err
  327. }
  328. // Only display response header.
  329. var respTrace []byte
  330. // For errors we make sure to dump response body as well.
  331. if resp.StatusCode != http.StatusOK &&
  332. resp.StatusCode != http.StatusPartialContent &&
  333. resp.StatusCode != http.StatusNoContent {
  334. respTrace, err = httputil.DumpResponse(resp, true)
  335. if err != nil {
  336. return err
  337. }
  338. } else {
  339. // WORKAROUND for https://github.com/golang/go/issues/13942.
  340. // httputil.DumpResponse does not print response headers for
  341. // all successful calls which have response ContentLength set
  342. // to zero. Keep this workaround until the above bug is fixed.
  343. if resp.ContentLength == 0 {
  344. var buffer bytes.Buffer
  345. if err = resp.Header.Write(&buffer); err != nil {
  346. return err
  347. }
  348. respTrace = buffer.Bytes()
  349. respTrace = append(respTrace, []byte("\r\n")...)
  350. } else {
  351. respTrace, err = httputil.DumpResponse(resp, false)
  352. if err != nil {
  353. return err
  354. }
  355. }
  356. }
  357. // Write response to trace output.
  358. _, err = fmt.Fprint(c.traceOutput, strings.TrimSuffix(string(respTrace), "\r\n"))
  359. if err != nil {
  360. return err
  361. }
  362. // Ends the http dump.
  363. _, err = fmt.Fprintln(c.traceOutput, "---------END-HTTP---------")
  364. if err != nil {
  365. return err
  366. }
  367. // Returns success.
  368. return nil
  369. }
  370. // do - execute http request.
  371. func (c Client) do(req *http.Request) (*http.Response, error) {
  372. var resp *http.Response
  373. var err error
  374. // Do the request in a loop in case of 307 http is met since golang still doesn't
  375. // handle properly this situation (https://github.com/golang/go/issues/7912)
  376. for {
  377. resp, err = c.httpClient.Do(req)
  378. if err != nil {
  379. // Handle this specifically for now until future Golang
  380. // versions fix this issue properly.
  381. urlErr, ok := err.(*url.Error)
  382. if ok && strings.Contains(urlErr.Err.Error(), "EOF") {
  383. return nil, &url.Error{
  384. Op: urlErr.Op,
  385. URL: urlErr.URL,
  386. Err: errors.New("Connection closed by foreign host " + urlErr.URL + ". Retry again."),
  387. }
  388. }
  389. return nil, err
  390. }
  391. // Redo the request with the new redirect url if http 307 is returned, quit the loop otherwise
  392. if resp != nil && resp.StatusCode == http.StatusTemporaryRedirect {
  393. newURL, err := url.Parse(resp.Header.Get("Location"))
  394. if err != nil {
  395. break
  396. }
  397. req.URL = newURL
  398. } else {
  399. break
  400. }
  401. }
  402. // Response cannot be non-nil, report if its the case.
  403. if resp == nil {
  404. msg := "Response is empty. " + reportIssue
  405. return nil, ErrInvalidArgument(msg)
  406. }
  407. // If trace is enabled, dump http request and response.
  408. if c.isTraceEnabled {
  409. err = c.dumpHTTP(req, resp)
  410. if err != nil {
  411. return nil, err
  412. }
  413. }
  414. return resp, nil
  415. }
  416. // List of success status.
  417. var successStatus = []int{
  418. http.StatusOK,
  419. http.StatusNoContent,
  420. http.StatusPartialContent,
  421. }
  422. // executeMethod - instantiates a given method, and retries the
  423. // request upon any error up to maxRetries attempts in a binomially
  424. // delayed manner using a standard back off algorithm.
  425. func (c Client) executeMethod(ctx context.Context, method string, metadata requestMetadata) (res *http.Response, err error) {
  426. var isRetryable bool // Indicates if request can be retried.
  427. var bodySeeker io.Seeker // Extracted seeker from io.Reader.
  428. var reqRetry = MaxRetry // Indicates how many times we can retry the request
  429. if metadata.contentBody != nil {
  430. // Check if body is seekable then it is retryable.
  431. bodySeeker, isRetryable = metadata.contentBody.(io.Seeker)
  432. switch bodySeeker {
  433. case os.Stdin, os.Stdout, os.Stderr:
  434. isRetryable = false
  435. }
  436. // Retry only when reader is seekable
  437. if !isRetryable {
  438. reqRetry = 1
  439. }
  440. // Figure out if the body can be closed - if yes
  441. // we will definitely close it upon the function
  442. // return.
  443. bodyCloser, ok := metadata.contentBody.(io.Closer)
  444. if ok {
  445. defer bodyCloser.Close()
  446. }
  447. }
  448. // Create a done channel to control 'newRetryTimer' go routine.
  449. doneCh := make(chan struct{}, 1)
  450. // Indicate to our routine to exit cleanly upon return.
  451. defer close(doneCh)
  452. // Blank indentifier is kept here on purpose since 'range' without
  453. // blank identifiers is only supported since go1.4
  454. // https://golang.org/doc/go1.4#forrange.
  455. for range c.newRetryTimer(reqRetry, DefaultRetryUnit, DefaultRetryCap, MaxJitter, doneCh) {
  456. // Retry executes the following function body if request has an
  457. // error until maxRetries have been exhausted, retry attempts are
  458. // performed after waiting for a given period of time in a
  459. // binomial fashion.
  460. if isRetryable {
  461. // Seek back to beginning for each attempt.
  462. if _, err = bodySeeker.Seek(0, 0); err != nil {
  463. // If seek failed, no need to retry.
  464. return nil, err
  465. }
  466. }
  467. // Instantiate a new request.
  468. var req *http.Request
  469. req, err = c.newRequest(method, metadata)
  470. if err != nil {
  471. errResponse := ToErrorResponse(err)
  472. if isS3CodeRetryable(errResponse.Code) {
  473. continue // Retry.
  474. }
  475. return nil, err
  476. }
  477. // Add context to request
  478. req = req.WithContext(ctx)
  479. // Initiate the request.
  480. res, err = c.do(req)
  481. if err != nil {
  482. // For supported network errors verify.
  483. if isNetErrorRetryable(err) {
  484. continue // Retry.
  485. }
  486. // For other errors, return here no need to retry.
  487. return nil, err
  488. }
  489. // For any known successful http status, return quickly.
  490. for _, httpStatus := range successStatus {
  491. if httpStatus == res.StatusCode {
  492. return res, nil
  493. }
  494. }
  495. // Read the body to be saved later.
  496. errBodyBytes, err := ioutil.ReadAll(res.Body)
  497. // res.Body should be closed
  498. closeResponse(res)
  499. if err != nil {
  500. return nil, err
  501. }
  502. // Save the body.
  503. errBodySeeker := bytes.NewReader(errBodyBytes)
  504. res.Body = ioutil.NopCloser(errBodySeeker)
  505. // For errors verify if its retryable otherwise fail quickly.
  506. errResponse := ToErrorResponse(httpRespToErrorResponse(res, metadata.bucketName, metadata.objectName))
  507. // Save the body back again.
  508. errBodySeeker.Seek(0, 0) // Seek back to starting point.
  509. res.Body = ioutil.NopCloser(errBodySeeker)
  510. // Bucket region if set in error response and the error
  511. // code dictates invalid region, we can retry the request
  512. // with the new region.
  513. //
  514. // Additionally we should only retry if bucketLocation and custom
  515. // region is empty.
  516. if metadata.bucketLocation == "" && c.region == "" {
  517. if errResponse.Code == "AuthorizationHeaderMalformed" || errResponse.Code == "InvalidRegion" {
  518. if metadata.bucketName != "" && errResponse.Region != "" {
  519. // Gather Cached location only if bucketName is present.
  520. if _, cachedLocationError := c.bucketLocCache.Get(metadata.bucketName); cachedLocationError != false {
  521. c.bucketLocCache.Set(metadata.bucketName, errResponse.Region)
  522. continue // Retry.
  523. }
  524. }
  525. }
  526. }
  527. // Verify if error response code is retryable.
  528. if isS3CodeRetryable(errResponse.Code) {
  529. continue // Retry.
  530. }
  531. // Verify if http status code is retryable.
  532. if isHTTPStatusRetryable(res.StatusCode) {
  533. continue // Retry.
  534. }
  535. // For all other cases break out of the retry loop.
  536. break
  537. }
  538. return res, err
  539. }
  540. // newRequest - instantiate a new HTTP request for a given method.
  541. func (c Client) newRequest(method string, metadata requestMetadata) (req *http.Request, err error) {
  542. // If no method is supplied default to 'POST'.
  543. if method == "" {
  544. method = "POST"
  545. }
  546. location := metadata.bucketLocation
  547. if location == "" {
  548. if metadata.bucketName != "" {
  549. // Gather location only if bucketName is present.
  550. location, err = c.getBucketLocation(metadata.bucketName)
  551. if err != nil {
  552. if ToErrorResponse(err).Code != "AccessDenied" {
  553. return nil, err
  554. }
  555. }
  556. // Upon AccessDenied error on fetching bucket location, default
  557. // to possible locations based on endpoint URL. This can usually
  558. // happen when GetBucketLocation() is disabled using IAM policies.
  559. }
  560. if location == "" {
  561. location = getDefaultLocation(c.endpointURL, c.region)
  562. }
  563. }
  564. // Construct a new target URL.
  565. targetURL, err := c.makeTargetURL(metadata.bucketName, metadata.objectName, location, metadata.queryValues)
  566. if err != nil {
  567. return nil, err
  568. }
  569. // Initialize a new HTTP request for the method.
  570. req, err = http.NewRequest(method, targetURL.String(), nil)
  571. if err != nil {
  572. return nil, err
  573. }
  574. // Get credentials from the configured credentials provider.
  575. value, err := c.credsProvider.Get()
  576. if err != nil {
  577. return nil, err
  578. }
  579. var (
  580. signerType = value.SignerType
  581. accessKeyID = value.AccessKeyID
  582. secretAccessKey = value.SecretAccessKey
  583. sessionToken = value.SessionToken
  584. )
  585. // Custom signer set then override the behavior.
  586. if c.overrideSignerType != credentials.SignatureDefault {
  587. signerType = c.overrideSignerType
  588. }
  589. // If signerType returned by credentials helper is anonymous,
  590. // then do not sign regardless of signerType override.
  591. if value.SignerType == credentials.SignatureAnonymous {
  592. signerType = credentials.SignatureAnonymous
  593. }
  594. // Generate presign url if needed, return right here.
  595. if metadata.expires != 0 && metadata.presignURL {
  596. if signerType.IsAnonymous() {
  597. return nil, ErrInvalidArgument("Presigned URLs cannot be generated with anonymous credentials.")
  598. }
  599. if signerType.IsV2() {
  600. // Presign URL with signature v2.
  601. req = s3signer.PreSignV2(*req, accessKeyID, secretAccessKey, metadata.expires)
  602. } else if signerType.IsV4() {
  603. // Presign URL with signature v4.
  604. req = s3signer.PreSignV4(*req, accessKeyID, secretAccessKey, sessionToken, location, metadata.expires)
  605. }
  606. return req, nil
  607. }
  608. // Set 'User-Agent' header for the request.
  609. c.setUserAgent(req)
  610. // Set all headers.
  611. for k, v := range metadata.customHeader {
  612. req.Header.Set(k, v[0])
  613. }
  614. // Go net/http notoriously closes the request body.
  615. // - The request Body, if non-nil, will be closed by the underlying Transport, even on errors.
  616. // This can cause underlying *os.File seekers to fail, avoid that
  617. // by making sure to wrap the closer as a nop.
  618. if metadata.contentLength == 0 {
  619. req.Body = nil
  620. } else {
  621. req.Body = ioutil.NopCloser(metadata.contentBody)
  622. }
  623. // Set incoming content-length.
  624. req.ContentLength = metadata.contentLength
  625. if req.ContentLength <= -1 {
  626. // For unknown content length, we upload using transfer-encoding: chunked.
  627. req.TransferEncoding = []string{"chunked"}
  628. }
  629. // set md5Sum for content protection.
  630. if len(metadata.contentMD5Base64) > 0 {
  631. req.Header.Set("Content-Md5", metadata.contentMD5Base64)
  632. }
  633. // For anonymous requests just return.
  634. if signerType.IsAnonymous() {
  635. return req, nil
  636. }
  637. switch {
  638. case signerType.IsV2():
  639. // Add signature version '2' authorization header.
  640. req = s3signer.SignV2(*req, accessKeyID, secretAccessKey)
  641. case metadata.objectName != "" && method == "PUT" && metadata.customHeader.Get("X-Amz-Copy-Source") == "" && !c.secure:
  642. // Streaming signature is used by default for a PUT object request. Additionally we also
  643. // look if the initialized client is secure, if yes then we don't need to perform
  644. // streaming signature.
  645. req = s3signer.StreamingSignV4(req, accessKeyID,
  646. secretAccessKey, sessionToken, location, metadata.contentLength, time.Now().UTC())
  647. default:
  648. // Set sha256 sum for signature calculation only with signature version '4'.
  649. shaHeader := unsignedPayload
  650. if metadata.contentSHA256Hex != "" {
  651. shaHeader = metadata.contentSHA256Hex
  652. }
  653. req.Header.Set("X-Amz-Content-Sha256", shaHeader)
  654. // Add signature version '4' authorization header.
  655. req = s3signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, location)
  656. }
  657. // Return request.
  658. return req, nil
  659. }
  660. // set User agent.
  661. func (c Client) setUserAgent(req *http.Request) {
  662. req.Header.Set("User-Agent", libraryUserAgent)
  663. if c.appInfo.appName != "" && c.appInfo.appVersion != "" {
  664. req.Header.Set("User-Agent", libraryUserAgent+" "+c.appInfo.appName+"/"+c.appInfo.appVersion)
  665. }
  666. }
  667. // makeTargetURL make a new target url.
  668. func (c Client) makeTargetURL(bucketName, objectName, bucketLocation string, queryValues url.Values) (*url.URL, error) {
  669. host := c.endpointURL.Host
  670. // For Amazon S3 endpoint, try to fetch location based endpoint.
  671. if s3utils.IsAmazonEndpoint(c.endpointURL) {
  672. if c.s3AccelerateEndpoint != "" && bucketName != "" {
  673. // http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
  674. // Disable transfer acceleration for non-compliant bucket names.
  675. if strings.Contains(bucketName, ".") {
  676. return nil, ErrTransferAccelerationBucket(bucketName)
  677. }
  678. // If transfer acceleration is requested set new host.
  679. // For more details about enabling transfer acceleration read here.
  680. // http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
  681. host = c.s3AccelerateEndpoint
  682. } else {
  683. // Do not change the host if the endpoint URL is a FIPS S3 endpoint.
  684. if !s3utils.IsAmazonFIPSGovCloudEndpoint(c.endpointURL) {
  685. // Fetch new host based on the bucket location.
  686. host = getS3Endpoint(bucketLocation)
  687. }
  688. }
  689. }
  690. // Save scheme.
  691. scheme := c.endpointURL.Scheme
  692. // Strip port 80 and 443 so we won't send these ports in Host header.
  693. // The reason is that browsers and curl automatically remove :80 and :443
  694. // with the generated presigned urls, then a signature mismatch error.
  695. if h, p, err := net.SplitHostPort(host); err == nil {
  696. if scheme == "http" && p == "80" || scheme == "https" && p == "443" {
  697. host = h
  698. }
  699. }
  700. urlStr := scheme + "://" + host + "/"
  701. // Make URL only if bucketName is available, otherwise use the
  702. // endpoint URL.
  703. if bucketName != "" {
  704. // Save if target url will have buckets which suppport virtual host.
  705. isVirtualHostStyle := s3utils.IsVirtualHostSupported(c.endpointURL, bucketName)
  706. // If endpoint supports virtual host style use that always.
  707. // Currently only S3 and Google Cloud Storage would support
  708. // virtual host style.
  709. if isVirtualHostStyle {
  710. urlStr = scheme + "://" + bucketName + "." + host + "/"
  711. if objectName != "" {
  712. urlStr = urlStr + s3utils.EncodePath(objectName)
  713. }
  714. } else {
  715. // If not fall back to using path style.
  716. urlStr = urlStr + bucketName + "/"
  717. if objectName != "" {
  718. urlStr = urlStr + s3utils.EncodePath(objectName)
  719. }
  720. }
  721. }
  722. // If there are any query values, add them to the end.
  723. if len(queryValues) > 0 {
  724. urlStr = urlStr + "?" + s3utils.QueryEncode(queryValues)
  725. }
  726. u, err := url.Parse(urlStr)
  727. if err != nil {
  728. return nil, err
  729. }
  730. return u, nil
  731. }