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  }