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