github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/s3/s3.go (about) 1 // 2 // goamz - Go packages to interact with the Amazon Web Services. 3 // 4 // https://wiki.ubuntu.com/goamz 5 // 6 // Copyright (c) 2011 Canonical Ltd. 7 // 8 // Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com> 9 // 10 // Modified by Keybase to allow external signing of requests. 11 12 package s3 13 14 import ( 15 "bytes" 16 "crypto/md5" 17 "encoding/base64" 18 "encoding/xml" 19 "fmt" 20 "io" 21 "log" 22 "net" 23 "net/http" 24 "net/http/httputil" 25 "net/url" 26 "strconv" 27 "strings" 28 "time" 29 30 "github.com/keybase/client/go/libkb" 31 32 "golang.org/x/net/context" 33 ) 34 35 const debug = false 36 37 type Signer interface { 38 Sign(payload []byte) ([]byte, error) 39 } 40 41 // The S3 type encapsulates operations with an S3 region. 42 type S3 struct { 43 Region 44 libkb.Contextified 45 46 // This is needed for payload construction. It's 47 // ok for clients to know it. 48 AccessKey string 49 50 // Signer signs payloads for s3 request authorization. 51 Signer Signer 52 53 // ConnectTimeout is the maximum time a request attempt will 54 // wait for a successful connection to be made. 55 // 56 // A value of zero means no timeout. 57 ConnectTimeout time.Duration 58 59 // ReadTimeout is the maximum time a request attempt will wait 60 // for an individual read to complete. 61 // 62 // A value of zero means no timeout. 63 ReadTimeout time.Duration 64 65 // WriteTimeout is the maximum time a request attempt will 66 // wait for an individual write to complete. 67 // 68 // A value of zero means no timeout. 69 WriteTimeout time.Duration 70 71 // RequestTimeout is the maximum time a request attempt can 72 // take before operations return a timeout error. 73 // 74 // This includes connection time, any redirects, and reading 75 // the response body. The timer remains running after the request 76 // is made so it can interrupt reading of the response data. 77 // 78 // A Timeout of zero means no timeout. 79 RequestTimeout time.Duration 80 81 // AttemptStrategy is the attempt strategy used for requests. 82 AttemptStrategy 83 84 // client used for requests 85 client *http.Client 86 } 87 88 // The Bucket type encapsulates operations with an S3 bucket. 89 type Bucket struct { 90 *S3 91 Name string 92 } 93 94 // The Owner type represents the owner of the object in an S3 bucket. 95 type Owner struct { 96 ID string 97 DisplayName string 98 } 99 100 // Fold options into an Options struct 101 type Options struct { 102 SSE bool 103 Meta map[string][]string 104 ContentEncoding string 105 CacheControl string 106 RedirectLocation string 107 ContentMD5 string 108 // What else? 109 // Content-Disposition string 110 //// The following become headers so they are []strings rather than strings... I think 111 // x-amz-storage-class []string 112 } 113 114 type CopyOptions struct { 115 Options 116 MetadataDirective string 117 ContentType string 118 } 119 120 // CopyObjectResult is the output from a Copy request 121 type CopyObjectResult struct { 122 ETag string 123 LastModified string 124 } 125 126 // DefaultAttemptStrategy is the default AttemptStrategy used by S3 objects created by New. 127 var DefaultAttemptStrategy = AttemptStrategy{ 128 Min: 5, 129 Total: 5 * time.Second, 130 Delay: 200 * time.Millisecond, 131 } 132 133 // New creates a new S3. Optional client argument allows for custom http.clients to be used. 134 func New(g *libkb.GlobalContext, signer Signer, region Region, client ...*http.Client) *S3 { 135 136 var httpclient *http.Client 137 138 if len(client) > 0 { 139 httpclient = client[0] 140 } 141 142 return &S3{ 143 Signer: signer, 144 Region: region, 145 AttemptStrategy: DefaultAttemptStrategy, 146 client: httpclient, 147 Contextified: libkb.NewContextified(g), 148 } 149 } 150 151 func (s3 *S3) SetAccessKey(key string) { 152 s3.AccessKey = key 153 } 154 155 // Bucket returns a Bucket with the given name. 156 func (s3 *S3) Bucket(name string) BucketInt { 157 if s3.Region.S3BucketEndpoint != "" || s3.Region.S3LowercaseBucket { 158 name = strings.ToLower(name) 159 } 160 return &Bucket{s3, name} 161 } 162 163 var createBucketConfiguration = `<CreateBucketConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> 164 <LocationConstraint>%s</LocationConstraint> 165 </CreateBucketConfiguration>` 166 167 // locationConstraint returns an io.Reader specifying a LocationConstraint if 168 // required for the region. 169 // 170 // See http://goo.gl/bh9Kq for details. 171 func (s3 *S3) locationConstraint() io.Reader { 172 constraint := "" 173 if s3.Region.S3LocationConstraint { 174 constraint = fmt.Sprintf(createBucketConfiguration, s3.Region.Name) 175 } 176 return strings.NewReader(constraint) 177 } 178 179 type ACL string 180 181 const ( 182 Private = ACL("private") 183 PublicRead = ACL("public-read") 184 PublicReadWrite = ACL("public-read-write") 185 AuthenticatedRead = ACL("authenticated-read") 186 BucketOwnerRead = ACL("bucket-owner-read") 187 BucketOwnerFull = ACL("bucket-owner-full-control") 188 ) 189 190 // PutBucket creates a new bucket. 191 // 192 // See http://goo.gl/ndjnR for details. 193 func (b *Bucket) PutBucket(ctx context.Context, perm ACL) error { 194 headers := map[string][]string{ 195 "x-amz-acl": {string(perm)}, 196 } 197 req := &request{ 198 method: "PUT", 199 bucket: b.Name, 200 path: "/", 201 headers: headers, 202 payload: b.locationConstraint(), 203 } 204 return b.S3.query(ctx, req, nil) 205 } 206 207 // DelBucket removes an existing S3 bucket. All objects in the bucket must 208 // be removed before the bucket itself can be removed. 209 // 210 // See http://goo.gl/GoBrY for details. 211 func (b *Bucket) DelBucket() (err error) { 212 req := &request{ 213 method: "DELETE", 214 bucket: b.Name, 215 path: "/", 216 } 217 for attempt := b.S3.AttemptStrategy.Start(); attempt.Next(); { 218 err = b.S3.query(context.Background(), req, nil) 219 if !shouldRetry(err) { 220 break 221 } 222 } 223 return err 224 } 225 226 // Get retrieves an object from an S3 bucket. 227 // 228 // See http://goo.gl/isCO7 for details. 229 func (b *Bucket) Get(ctx context.Context, path string) (data []byte, err error) { 230 body, err := b.GetReader(ctx, path) 231 defer func() { 232 if body != nil { 233 body.Close() 234 } 235 }() 236 if err != nil { 237 return nil, err 238 } 239 data, err = io.ReadAll(body) 240 return data, err 241 } 242 243 // GetReader retrieves an object from an S3 bucket, 244 // returning the body of the HTTP response. 245 // It is the caller's responsibility to call Close on rc when 246 // finished reading. 247 func (b *Bucket) GetReader(ctx context.Context, path string) (rc io.ReadCloser, err error) { 248 resp, err := b.GetResponse(ctx, path) 249 if resp != nil { 250 return resp.Body, err 251 } 252 return nil, err 253 } 254 255 // GetReaderWithRange retrieves an object from an S3 bucket using the specified range, 256 // returning the body of the HTTP response. 257 // It is the caller's responsibility to call Close on rc when 258 // finished reading. 259 func (b *Bucket) GetReaderWithRange(ctx context.Context, path string, begin, end int64) (rc io.ReadCloser, err error) { 260 header := make(http.Header) 261 header.Add("Range", fmt.Sprintf("bytes=%d-%d", begin, end-1)) 262 resp, err := b.GetResponseWithHeaders(ctx, path, header) 263 if resp != nil { 264 return resp.Body, err 265 } 266 return nil, err 267 } 268 269 // GetResponse retrieves an object from an S3 bucket, 270 // returning the HTTP response. 271 // It is the caller's responsibility to call Close on rc when 272 // finished reading 273 func (b *Bucket) GetResponse(ctx context.Context, path string) (resp *http.Response, err error) { 274 return b.GetResponseWithHeaders(ctx, path, make(http.Header)) 275 } 276 277 // GetReaderWithHeaders retrieves an object from an S3 bucket 278 // Accepts custom headers to be sent as the second parameter 279 // returning the body of the HTTP response. 280 // It is the caller's responsibility to call Close on rc when 281 // finished reading 282 func (b *Bucket) GetResponseWithHeaders(ctx context.Context, path string, headers map[string][]string) (resp *http.Response, err error) { 283 req := &request{ 284 bucket: b.Name, 285 path: path, 286 headers: headers, 287 } 288 err = b.S3.prepare(req) 289 if err != nil { 290 return nil, err 291 } 292 for attempt := b.S3.AttemptStrategy.Start(); attempt.Next(); { 293 resp, err := b.S3.run(ctx, req, nil) 294 if shouldRetry(err) && attempt.HasNext() { 295 continue 296 } 297 if err != nil { 298 return nil, err 299 } 300 return resp, nil 301 } 302 panic("unreachable") 303 } 304 305 // Exists checks whether or not an object exists on an S3 bucket using a HEAD request. 306 func (b *Bucket) Exists(path string) (exists bool, err error) { 307 req := &request{ 308 method: "HEAD", 309 bucket: b.Name, 310 path: path, 311 } 312 err = b.S3.prepare(req) 313 if err != nil { 314 return 315 } 316 for attempt := b.S3.AttemptStrategy.Start(); attempt.Next(); { 317 resp, err := b.S3.run(context.Background(), req, nil) 318 319 if shouldRetry(err) && attempt.HasNext() { 320 continue 321 } 322 323 if err != nil { 324 // We can treat a 403 or 404 as non existence 325 if e, ok := err.(*Error); ok && (e.StatusCode == 403 || e.StatusCode == 404) { 326 return false, nil 327 } 328 return false, err 329 } 330 331 if resp.StatusCode/100 == 2 { 332 exists = true 333 } 334 return exists, err 335 } 336 return false, fmt.Errorf("S3 Currently Unreachable") 337 } 338 339 // Head HEADs an object in the S3 bucket, returns the response with 340 // no body see http://bit.ly/17K1ylI 341 func (b *Bucket) Head(path string, headers map[string][]string) (*http.Response, error) { 342 req := &request{ 343 method: "HEAD", 344 bucket: b.Name, 345 path: path, 346 headers: headers, 347 } 348 err := b.S3.prepare(req) 349 if err != nil { 350 return nil, err 351 } 352 353 for attempt := b.S3.AttemptStrategy.Start(); attempt.Next(); { 354 resp, err := b.S3.run(context.Background(), req, nil) 355 if shouldRetry(err) && attempt.HasNext() { 356 continue 357 } 358 if err != nil { 359 return nil, err 360 } 361 return resp, err 362 } 363 return nil, fmt.Errorf("S3 Currently Unreachable") 364 } 365 366 // Put inserts an object into the S3 bucket. 367 // 368 // See http://goo.gl/FEBPD for details. 369 func (b *Bucket) Put(ctx context.Context, path string, data []byte, contType string, perm ACL, options Options) error { 370 body := bytes.NewBuffer(data) 371 return b.PutReader(ctx, path, body, int64(len(data)), contType, perm, options) 372 } 373 374 // PutCopy puts a copy of an object given by the key path into bucket b using b.Path as the target key 375 func (b *Bucket) PutCopy(path string, perm ACL, options CopyOptions, source string) (result *CopyObjectResult, err error) { 376 headers := map[string][]string{ 377 "x-amz-acl": {string(perm)}, 378 "x-amz-copy-source": {source}, 379 } 380 options.addHeaders(headers) 381 req := &request{ 382 method: "PUT", 383 bucket: b.Name, 384 path: path, 385 headers: headers, 386 } 387 result = &CopyObjectResult{} 388 for attempt := b.S3.AttemptStrategy.Start(); attempt.Next(); { 389 err = b.S3.query(context.Background(), req, result) 390 if !shouldRetry(err) { 391 break 392 } 393 } 394 if err != nil { 395 return nil, err 396 } 397 return result, nil 398 } 399 400 /* 401 PutHeader - like Put, inserts an object into the S3 bucket. 402 Instead of Content-Type string, pass in custom headers to override defaults. 403 */ 404 func (b *Bucket) PutHeader(ctx context.Context, path string, data []byte, customHeaders map[string][]string, perm ACL) error { 405 body := bytes.NewBuffer(data) 406 return b.PutReaderHeader(ctx, path, body, int64(len(data)), customHeaders, perm) 407 } 408 409 // PutReader inserts an object into the S3 bucket by consuming data 410 // from r until EOF. 411 func (b *Bucket) PutReader(ctx context.Context, path string, r io.Reader, length int64, contType string, perm ACL, options Options) error { 412 headers := map[string][]string{ 413 "Content-Length": {strconv.FormatInt(length, 10)}, 414 "Content-Type": {contType}, 415 "x-amz-acl": {string(perm)}, 416 } 417 options.addHeaders(headers) 418 req := &request{ 419 method: "PUT", 420 bucket: b.Name, 421 path: path, 422 headers: headers, 423 payload: r, 424 } 425 return b.S3.query(ctx, req, nil) 426 } 427 428 /* 429 PutReaderHeader - like PutReader, inserts an object into S3 from a reader. 430 Instead of Content-Type string, pass in custom headers to override defaults. 431 */ 432 func (b *Bucket) PutReaderHeader(ctx context.Context, path string, r io.Reader, length int64, customHeaders map[string][]string, perm ACL) error { 433 // Default headers 434 headers := map[string][]string{ 435 "Content-Length": {strconv.FormatInt(length, 10)}, 436 "Content-Type": {"application/text"}, 437 "x-amz-acl": {string(perm)}, 438 } 439 440 // Override with custom headers 441 for key, value := range customHeaders { 442 headers[key] = value 443 } 444 445 req := &request{ 446 method: "PUT", 447 bucket: b.Name, 448 path: path, 449 headers: headers, 450 payload: r, 451 } 452 return b.S3.query(ctx, req, nil) 453 } 454 455 // addHeaders adds o's specified fields to headers 456 func (o Options) addHeaders(headers map[string][]string) { 457 if o.SSE { 458 headers["x-amz-server-side-encryption"] = []string{"AES256"} 459 } 460 if len(o.ContentEncoding) != 0 { 461 headers["Content-Encoding"] = []string{o.ContentEncoding} 462 } 463 if len(o.CacheControl) != 0 { 464 headers["Cache-Control"] = []string{o.CacheControl} 465 } 466 if len(o.ContentMD5) != 0 { 467 headers["Content-MD5"] = []string{o.ContentMD5} 468 } 469 if len(o.RedirectLocation) != 0 { 470 headers["x-amz-website-redirect-location"] = []string{o.RedirectLocation} 471 } 472 for k, v := range o.Meta { 473 headers["x-amz-meta-"+k] = v 474 } 475 } 476 477 // addHeaders adds o's specified fields to headers 478 func (o CopyOptions) addHeaders(headers map[string][]string) { 479 o.Options.addHeaders(headers) 480 if len(o.MetadataDirective) != 0 { 481 headers["x-amz-metadata-directive"] = []string{o.MetadataDirective} 482 } 483 if len(o.ContentType) != 0 { 484 headers["Content-Type"] = []string{o.ContentType} 485 } 486 } 487 488 func makeXMLBuffer(doc []byte) *bytes.Buffer { 489 buf := new(bytes.Buffer) 490 buf.WriteString(xml.Header) 491 buf.Write(doc) 492 return buf 493 } 494 495 type RoutingRule struct { 496 ConditionKeyPrefixEquals string `xml:"Condition>KeyPrefixEquals"` 497 RedirectReplaceKeyPrefixWith string `xml:"Redirect>ReplaceKeyPrefixWith,omitempty"` 498 RedirectReplaceKeyWith string `xml:"Redirect>ReplaceKeyWith,omitempty"` 499 } 500 501 type WebsiteConfiguration struct { 502 XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ WebsiteConfiguration"` 503 IndexDocumentSuffix string `xml:"IndexDocument>Suffix"` 504 ErrorDocumentKey string `xml:"ErrorDocument>Key"` 505 RoutingRules *[]RoutingRule `xml:"RoutingRules>RoutingRule,omitempty"` 506 } 507 508 func (b *Bucket) PutBucketWebsite(configuration WebsiteConfiguration) error { 509 510 doc, err := xml.Marshal(configuration) 511 if err != nil { 512 return err 513 } 514 515 buf := makeXMLBuffer(doc) 516 517 return b.PutBucketSubresource("website", buf, int64(buf.Len())) 518 } 519 520 func (b *Bucket) PutBucketSubresource(subresource string, r io.Reader, length int64) error { 521 headers := map[string][]string{ 522 "Content-Length": {strconv.FormatInt(length, 10)}, 523 } 524 req := &request{ 525 path: "/", 526 method: "PUT", 527 bucket: b.Name, 528 headers: headers, 529 payload: r, 530 params: url.Values{subresource: {""}}, 531 } 532 533 return b.S3.query(context.Background(), req, nil) 534 } 535 536 // Del removes an object from the S3 bucket. 537 // 538 // See http://goo.gl/APeTt for details. 539 func (b *Bucket) Del(ctx context.Context, path string) error { 540 req := &request{ 541 method: "DELETE", 542 bucket: b.Name, 543 path: path, 544 } 545 return b.S3.query(ctx, req, nil) 546 } 547 548 type Delete struct { 549 Quiet bool `xml:"Quiet,omitempty"` 550 Objects []Object `xml:"Object"` 551 } 552 553 type Object struct { 554 Key string `xml:"Key"` 555 VersionID string `xml:"VersionId,omitempty"` 556 } 557 558 // DelMulti removes up to 1000 objects from the S3 bucket. 559 // 560 // See http://goo.gl/jx6cWK for details. 561 func (b *Bucket) DelMulti(objects Delete) error { 562 doc, err := xml.Marshal(objects) 563 if err != nil { 564 return err 565 } 566 567 buf := makeXMLBuffer(doc) 568 digest := md5.New() 569 size, err := digest.Write(buf.Bytes()) 570 if err != nil { 571 return err 572 } 573 574 headers := map[string][]string{ 575 "Content-Length": {strconv.FormatInt(int64(size), 10)}, 576 "Content-MD5": {base64.StdEncoding.EncodeToString(digest.Sum(nil))}, 577 "Content-Type": {"text/xml"}, 578 } 579 req := &request{ 580 path: "/", 581 method: "POST", 582 params: url.Values{"delete": {""}}, 583 bucket: b.Name, 584 headers: headers, 585 payload: buf, 586 } 587 588 return b.S3.query(context.Background(), req, nil) 589 } 590 591 // The ListResp type holds the results of a List bucket operation. 592 type ListResp struct { 593 Name string 594 Prefix string 595 Delimiter string 596 Marker string 597 NextMarker string 598 MaxKeys int 599 600 // IsTruncated is true if the results have been truncated because 601 // there are more keys and prefixes than can fit in MaxKeys. 602 // N.B. this is the opposite sense to that documented (incorrectly) in 603 // http://goo.gl/YjQTc 604 IsTruncated bool 605 Contents []Key 606 CommonPrefixes []string `xml:">Prefix"` 607 } 608 609 // The Key type represents an item stored in an S3 bucket. 610 type Key struct { 611 Key string 612 LastModified string 613 Size int64 614 // ETag gives the hex-encoded MD5 sum of the contents, 615 // surrounded with double-quotes. 616 ETag string 617 StorageClass string 618 Owner Owner 619 } 620 621 // List returns information about objects in an S3 bucket. 622 // 623 // The prefix parameter limits the response to keys that begin with the 624 // specified prefix. 625 // 626 // The delim parameter causes the response to group all of the keys that 627 // share a common prefix up to the next delimiter in a single entry within 628 // the CommonPrefixes field. You can use delimiters to separate a bucket 629 // into different groupings of keys, similar to how folders would work. 630 // 631 // The marker parameter specifies the key to start with when listing objects 632 // in a bucket. Amazon S3 lists objects in alphabetical order and 633 // will return keys alphabetically greater than the marker. 634 // 635 // The max parameter specifies how many keys + common prefixes to return in 636 // the response. The default is 1000. 637 // 638 // For example, given these keys in a bucket: 639 // 640 // index.html 641 // index2.html 642 // photos/2006/January/sample.jpg 643 // photos/2006/February/sample2.jpg 644 // photos/2006/February/sample3.jpg 645 // photos/2006/February/sample4.jpg 646 // 647 // Listing this bucket with delimiter set to "/" would yield the 648 // following result: 649 // 650 // &ListResp{ 651 // Name: "sample-bucket", 652 // MaxKeys: 1000, 653 // Delimiter: "/", 654 // Contents: []Key{ 655 // {Key: "index.html", "index2.html"}, 656 // }, 657 // CommonPrefixes: []string{ 658 // "photos/", 659 // }, 660 // } 661 // 662 // Listing the same bucket with delimiter set to "/" and prefix set to 663 // "photos/2006/" would yield the following result: 664 // 665 // &ListResp{ 666 // Name: "sample-bucket", 667 // MaxKeys: 1000, 668 // Delimiter: "/", 669 // Prefix: "photos/2006/", 670 // CommonPrefixes: []string{ 671 // "photos/2006/February/", 672 // "photos/2006/January/", 673 // }, 674 // } 675 // 676 // See http://goo.gl/YjQTc for details. 677 func (b *Bucket) List(prefix, delim, marker string, max int) (result *ListResp, err error) { 678 params := map[string][]string{ 679 "prefix": {prefix}, 680 "delimiter": {delim}, 681 "marker": {marker}, 682 } 683 if max != 0 { 684 params["max-keys"] = []string{strconv.FormatInt(int64(max), 10)} 685 } 686 req := &request{ 687 bucket: b.Name, 688 params: params, 689 } 690 result = &ListResp{} 691 for attempt := b.S3.AttemptStrategy.Start(); attempt.Next(); { 692 err = b.S3.query(context.Background(), req, result) 693 if !shouldRetry(err) { 694 break 695 } 696 } 697 if err != nil { 698 return nil, err 699 } 700 return result, nil 701 } 702 703 // The VersionsResp type holds the results of a list bucket Versions operation. 704 type VersionsResp struct { 705 Name string 706 Prefix string 707 KeyMarker string 708 VersionIDMarker string `xml:"VersionIdMarker"` 709 MaxKeys int 710 Delimiter string 711 IsTruncated bool 712 Versions []Version 713 CommonPrefixes []string `xml:">Prefix"` 714 } 715 716 // The Version type represents an object version stored in an S3 bucket. 717 type Version struct { 718 Key string 719 VersionID string `xml:"VersionId"` 720 IsLatest bool 721 LastModified string 722 // ETag gives the hex-encoded MD5 sum of the contents, 723 // surrounded with double-quotes. 724 ETag string 725 Size int64 726 Owner Owner 727 StorageClass string 728 } 729 730 func (b *Bucket) Versions(prefix, delim, keyMarker string, versionIDMarker string, max int) (result *VersionsResp, err error) { 731 params := map[string][]string{ 732 "versions": {""}, 733 "prefix": {prefix}, 734 "delimiter": {delim}, 735 } 736 737 if len(versionIDMarker) != 0 { 738 params["version-id-marker"] = []string{versionIDMarker} 739 } 740 if len(keyMarker) != 0 { 741 params["key-marker"] = []string{keyMarker} 742 } 743 744 if max != 0 { 745 params["max-keys"] = []string{strconv.FormatInt(int64(max), 10)} 746 } 747 req := &request{ 748 bucket: b.Name, 749 params: params, 750 } 751 result = &VersionsResp{} 752 for attempt := b.S3.AttemptStrategy.Start(); attempt.Next(); { 753 err = b.S3.query(context.Background(), req, result) 754 if !shouldRetry(err) { 755 break 756 } 757 } 758 if err != nil { 759 return nil, err 760 } 761 return result, nil 762 } 763 764 // Returns a mapping of all key names in this bucket to Key objects 765 func (b *Bucket) GetBucketContents() (*map[string]Key, error) { 766 bucketContents := map[string]Key{} 767 prefix := "" 768 pathSeparator := "" 769 marker := "" 770 for { 771 contents, err := b.List(prefix, pathSeparator, marker, 1000) 772 if err != nil { 773 return &bucketContents, err 774 } 775 for _, key := range contents.Contents { 776 bucketContents[key.Key] = key 777 } 778 if contents.IsTruncated { 779 marker = contents.NextMarker 780 } else { 781 break 782 } 783 } 784 785 return &bucketContents, nil 786 } 787 788 // URL returns a non-signed URL that allows retrieving the 789 // object at path. It only works if the object is publicly 790 // readable (see SignedURL). 791 func (b *Bucket) URL(path string) string { 792 req := &request{ 793 bucket: b.Name, 794 path: path, 795 } 796 err := b.S3.prepare(req) 797 if err != nil { 798 panic(err) 799 } 800 u, err := req.url() 801 if err != nil { 802 panic(err) 803 } 804 u.RawQuery = "" 805 return u.String() 806 } 807 808 // SignedURL returns a signed URL that allows anyone holding the URL 809 // to retrieve the object at path. The signature is valid until expires. 810 func (b *Bucket) SignedURL(path string, expires time.Time) string { 811 req := &request{ 812 bucket: b.Name, 813 path: path, 814 params: url.Values{"Expires": {strconv.FormatInt(expires.Unix(), 10)}}, 815 } 816 err := b.S3.prepare(req) 817 if err != nil { 818 panic(err) 819 } 820 u, err := req.url() 821 if err != nil { 822 panic(err) 823 } 824 return u.String() 825 } 826 827 type request struct { 828 method string 829 bucket string 830 path string 831 params url.Values 832 headers http.Header 833 baseurl string 834 payload io.Reader 835 prepared bool 836 } 837 838 func (req *request) url() (*url.URL, error) { 839 u, err := url.Parse(req.baseurl) 840 if err != nil { 841 return nil, fmt.Errorf("bad S3 endpoint URL %q: %v", req.baseurl, err) 842 } 843 u.RawQuery = req.params.Encode() 844 u.Path = req.path 845 return u, nil 846 } 847 848 // query prepares and runs the req request. 849 // If resp is not nil, the XML data contained in the response 850 // body will be unmarshalled on it. 851 func (s3 *S3) query(ctx context.Context, req *request, resp interface{}) error { 852 err := s3.prepare(req) 853 if err == nil { 854 var httpResponse *http.Response 855 httpResponse, err = s3.run(ctx, req, resp) 856 if resp == nil && httpResponse != nil { 857 httpResponse.Body.Close() 858 } 859 } 860 return err 861 } 862 863 // prepare sets up req to be delivered to S3. 864 func (s3 *S3) prepare(req *request) error { 865 var signpath = req.path 866 867 if !req.prepared { 868 req.prepared = true 869 if req.method == "" { 870 req.method = "GET" 871 } 872 // Copy so they can be mutated without affecting on retries. 873 params := make(url.Values) 874 headers := make(http.Header) 875 for k, v := range req.params { 876 params[k] = v 877 } 878 for k, v := range req.headers { 879 headers[k] = v 880 } 881 req.params = params 882 req.headers = headers 883 if !strings.HasPrefix(req.path, "/") { 884 req.path = "/" + req.path 885 } 886 signpath = req.path 887 if req.bucket != "" { 888 req.baseurl = s3.Region.S3BucketEndpoint 889 if req.baseurl == "" { 890 // Use the path method to address the bucket. 891 req.baseurl = s3.Region.S3Endpoint 892 req.path = "/" + req.bucket + req.path 893 } else { 894 // Just in case, prevent injection. 895 if strings.ContainsAny(req.bucket, "/:@") { 896 return fmt.Errorf("bad S3 bucket: %q", req.bucket) 897 } 898 req.baseurl = strings.ReplaceAll(req.baseurl, "${bucket}", req.bucket) 899 } 900 signpath = "/" + req.bucket + signpath 901 } 902 } 903 904 // Always sign again as it's not clear how far the 905 // server has handled a previous attempt. 906 u, err := url.Parse(req.baseurl) 907 if err != nil { 908 return fmt.Errorf("bad S3 endpoint URL %q: %v", req.baseurl, err) 909 } 910 reqSignpathSpaceFix := (&url.URL{Path: signpath}).String() 911 req.headers["Host"] = []string{u.Host} 912 req.headers["Date"] = []string{time.Now().In(time.UTC).Format(time.RFC1123)} 913 return s3.sign(req.method, reqSignpathSpaceFix, req.params, req.headers) 914 } 915 916 // run sends req and returns the http response from the server. 917 // If resp is not nil, the XML data contained in the response 918 // body will be unmarshalled on it. 919 func (s3 *S3) run(ctx context.Context, req *request, resp interface{}) (*http.Response, error) { 920 if debug { 921 log.Printf("Running S3 request: %#v", req) 922 } 923 924 u, err := req.url() 925 if err != nil { 926 return nil, err 927 } 928 929 hreq := &http.Request{ 930 URL: u, 931 Method: req.method, 932 ProtoMajor: 1, 933 ProtoMinor: 1, 934 Close: true, 935 Header: req.headers, 936 } 937 938 if ctx != nil { 939 hreq = hreq.WithContext(ctx) 940 } 941 942 if v, ok := req.headers["Content-Length"]; ok { 943 hreq.ContentLength, _ = strconv.ParseInt(v[0], 10, 64) 944 delete(req.headers, "Content-Length") 945 } 946 if req.payload != nil { 947 hreq.Body = io.NopCloser(req.payload) 948 } 949 950 if s3.client == nil { 951 s3.client = &http.Client{ 952 Transport: &http.Transport{ 953 Dial: func(netw, addr string) (c net.Conn, err error) { 954 c, err = net.DialTimeout(netw, addr, s3.ConnectTimeout) 955 if err != nil { 956 return 957 } 958 959 var deadline time.Time 960 if s3.RequestTimeout > 0 { 961 deadline = time.Now().Add(s3.RequestTimeout) 962 err := c.SetDeadline(deadline) 963 if err != nil { 964 return nil, err 965 } 966 } 967 968 if s3.ReadTimeout > 0 || s3.WriteTimeout > 0 { 969 c = &ioTimeoutConn{ 970 TCPConn: c.(*net.TCPConn), 971 readTimeout: s3.ReadTimeout, 972 writeTimeout: s3.WriteTimeout, 973 requestDeadline: deadline, 974 } 975 } 976 return 977 }, 978 Proxy: libkb.MakeProxy(s3.G().Env), 979 }, 980 } 981 } 982 983 hresp, err := s3.client.Do(hreq) 984 if err != nil { 985 return nil, err 986 } 987 if debug { 988 dump, _ := httputil.DumpResponse(hresp, true) 989 log.Printf("} -> %s\n", dump) 990 } 991 if hresp.StatusCode != 200 && hresp.StatusCode != 204 && hresp.StatusCode != 206 { 992 defer hresp.Body.Close() 993 return nil, buildError(hresp) 994 } 995 if resp != nil { 996 err = xml.NewDecoder(hresp.Body).Decode(resp) 997 hresp.Body.Close() 998 if debug { 999 log.Printf("goamz.s3> decoded xml into %#v", resp) 1000 } 1001 } 1002 return hresp, err 1003 } 1004 1005 // Error represents an error in an operation with S3. 1006 type Error struct { 1007 StatusCode int // HTTP status code (200, 403, ...) 1008 Code string // EC2 error code ("UnsupportedOperation", ...) 1009 Message string // The human-oriented error message 1010 BucketName string 1011 RequestID string `xml:"RequestId"` 1012 HostID string `xml:"HostId"` 1013 } 1014 1015 func (e *Error) Error() string { 1016 return e.Message 1017 } 1018 1019 func buildError(r *http.Response) error { 1020 if debug { 1021 log.Printf("got error (status code %v)", r.StatusCode) 1022 data, err := io.ReadAll(r.Body) 1023 if err != nil { 1024 log.Printf("\tread error: %v", err) 1025 } else { 1026 log.Printf("\tdata:\n%s\n\n", data) 1027 } 1028 r.Body = io.NopCloser(bytes.NewBuffer(data)) 1029 } 1030 1031 err := Error{} 1032 1033 // TODO return error if Unmarshal fails? 1034 decodeErr := xml.NewDecoder(r.Body).Decode(&err) 1035 if decodeErr != nil { 1036 log.Printf("\tdecodeErr error: %v", decodeErr) 1037 } 1038 r.Body.Close() 1039 err.StatusCode = r.StatusCode 1040 if err.Message == "" { 1041 err.Message = r.Status 1042 } 1043 if debug { 1044 log.Printf("err: %#v\n", err) 1045 } 1046 return &err 1047 } 1048 1049 func shouldRetry(err error) bool { 1050 if err == nil { 1051 return false 1052 } 1053 if e, ok := err.(*url.Error); ok { 1054 // Transport returns this string if it detects a write on a connection which 1055 // has already had an error 1056 if e.Err.Error() == "http: can't write HTTP request on broken connection" { 1057 return true 1058 } 1059 err = e.Err 1060 } 1061 1062 switch err { 1063 case io.ErrUnexpectedEOF, io.EOF: 1064 return true 1065 } 1066 switch e := err.(type) { 1067 case *net.DNSError: 1068 return true 1069 case *net.OpError: 1070 switch e.Op { 1071 case "read", "write", "WSARecv", "WSASend", "ConnectEx": 1072 return true 1073 } 1074 case *Error: 1075 switch e.Code { 1076 case "InternalError", "NoSuchUpload", "NoSuchBucket", "RequestTimeout": 1077 return true 1078 } 1079 // let's handle tls handshake timeout issues and similar temporary errors 1080 case net.Error: 1081 return e.Temporary() //nolint 1082 } 1083 1084 return false 1085 } 1086 1087 func hasCode(err error, code string) bool { 1088 s3err, ok := err.(*Error) 1089 return ok && s3err.Code == code 1090 } 1091 1092 // ioTimeoutConn is a net.Conn which sets a deadline for each Read or Write operation 1093 type ioTimeoutConn struct { 1094 *net.TCPConn 1095 readTimeout time.Duration 1096 writeTimeout time.Duration 1097 requestDeadline time.Time 1098 } 1099 1100 func (c *ioTimeoutConn) deadline(timeout time.Duration) time.Time { 1101 dl := time.Now().Add(timeout) 1102 if c.requestDeadline.IsZero() || dl.Before(c.requestDeadline) { 1103 return dl 1104 } 1105 1106 return c.requestDeadline 1107 } 1108 1109 func (c *ioTimeoutConn) Read(b []byte) (int, error) { 1110 if c.readTimeout > 0 { 1111 err := c.TCPConn.SetReadDeadline(c.deadline(c.readTimeout)) 1112 if err != nil { 1113 return 0, err 1114 } 1115 } 1116 return c.TCPConn.Read(b) 1117 } 1118 1119 func (c *ioTimeoutConn) Write(b []byte) (int, error) { 1120 if c.writeTimeout > 0 { 1121 err := c.TCPConn.SetWriteDeadline(c.deadline(c.writeTimeout)) 1122 if err != nil { 1123 return 0, err 1124 } 1125 } 1126 return c.TCPConn.Write(b) 1127 }