github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/api-response.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"context"
    22  	"encoding/base64"
    23  	"encoding/xml"
    24  	"fmt"
    25  	"net/http"
    26  	"net/url"
    27  	"path"
    28  	"strconv"
    29  	"strings"
    30  	"time"
    31  
    32  	"github.com/minio/minio/internal/amztime"
    33  	"github.com/minio/minio/internal/crypto"
    34  	"github.com/minio/minio/internal/handlers"
    35  	"github.com/minio/minio/internal/hash"
    36  	xhttp "github.com/minio/minio/internal/http"
    37  	"github.com/minio/minio/internal/logger"
    38  	"github.com/minio/pkg/v2/policy"
    39  	xxml "github.com/minio/xxml"
    40  )
    41  
    42  const (
    43  	maxObjectList  = 1000  // Limit number of objects in a listObjectsResponse/listObjectsVersionsResponse.
    44  	maxDeleteList  = 1000  // Limit number of objects deleted in a delete call.
    45  	maxUploadsList = 10000 // Limit number of uploads in a listUploadsResponse.
    46  	maxPartsList   = 10000 // Limit number of parts in a listPartsResponse.
    47  )
    48  
    49  // LocationResponse - format for location response.
    50  type LocationResponse struct {
    51  	XMLName  xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ LocationConstraint" json:"-"`
    52  	Location string   `xml:",chardata"`
    53  }
    54  
    55  // PolicyStatus captures information returned by GetBucketPolicyStatusHandler
    56  type PolicyStatus struct {
    57  	XMLName  xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ PolicyStatus" json:"-"`
    58  	IsPublic string
    59  }
    60  
    61  // ListVersionsResponse - format for list bucket versions response.
    62  type ListVersionsResponse struct {
    63  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListVersionsResult" json:"-"`
    64  
    65  	Name      string
    66  	Prefix    string
    67  	KeyMarker string
    68  
    69  	// When response is truncated (the IsTruncated element value in the response
    70  	// is true), you can use the key name in this field as marker in the subsequent
    71  	// request to get next set of objects. Server lists objects in alphabetical
    72  	// order Note: This element is returned only if you have delimiter request parameter
    73  	// specified. If response does not include the NextMaker and it is truncated,
    74  	// you can use the value of the last Key in the response as the marker in the
    75  	// subsequent request to get the next set of object keys.
    76  	NextKeyMarker string `xml:"NextKeyMarker,omitempty"`
    77  
    78  	// When the number of responses exceeds the value of MaxKeys,
    79  	// NextVersionIdMarker specifies the first object version not
    80  	// returned that satisfies the search criteria. Use this value
    81  	// for the version-id-marker request parameter in a subsequent request.
    82  	NextVersionIDMarker string `xml:"NextVersionIdMarker"`
    83  
    84  	// Marks the last version of the Key returned in a truncated response.
    85  	VersionIDMarker string `xml:"VersionIdMarker"`
    86  
    87  	MaxKeys   int
    88  	Delimiter string `xml:"Delimiter,omitempty"`
    89  	// A flag that indicates whether or not ListObjects returned all of the results
    90  	// that satisfied the search criteria.
    91  	IsTruncated bool
    92  
    93  	CommonPrefixes []CommonPrefix
    94  	Versions       []ObjectVersion
    95  
    96  	// Encoding type used to encode object keys in the response.
    97  	EncodingType string `xml:"EncodingType,omitempty"`
    98  }
    99  
   100  // ListObjectsResponse - format for list objects response.
   101  type ListObjectsResponse struct {
   102  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListBucketResult" json:"-"`
   103  
   104  	Name   string
   105  	Prefix string
   106  	Marker string
   107  
   108  	// When response is truncated (the IsTruncated element value in the response
   109  	// is true), you can use the key name in this field as marker in the subsequent
   110  	// request to get next set of objects. Server lists objects in alphabetical
   111  	// order Note: This element is returned only if you have delimiter request parameter
   112  	// specified. If response does not include the NextMaker and it is truncated,
   113  	// you can use the value of the last Key in the response as the marker in the
   114  	// subsequent request to get the next set of object keys.
   115  	NextMarker string `xml:"NextMarker,omitempty"`
   116  
   117  	MaxKeys   int
   118  	Delimiter string `xml:"Delimiter,omitempty"`
   119  	// A flag that indicates whether or not ListObjects returned all of the results
   120  	// that satisfied the search criteria.
   121  	IsTruncated bool
   122  
   123  	Contents       []Object
   124  	CommonPrefixes []CommonPrefix
   125  
   126  	// Encoding type used to encode object keys in the response.
   127  	EncodingType string `xml:"EncodingType,omitempty"`
   128  }
   129  
   130  // ListObjectsV2Response - format for list objects response.
   131  type ListObjectsV2Response struct {
   132  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListBucketResult" json:"-"`
   133  
   134  	Name       string
   135  	Prefix     string
   136  	StartAfter string `xml:"StartAfter,omitempty"`
   137  	// When response is truncated (the IsTruncated element value in the response
   138  	// is true), you can use the key name in this field as marker in the subsequent
   139  	// request to get next set of objects. Server lists objects in alphabetical
   140  	// order Note: This element is returned only if you have delimiter request parameter
   141  	// specified. If response does not include the NextMaker and it is truncated,
   142  	// you can use the value of the last Key in the response as the marker in the
   143  	// subsequent request to get the next set of object keys.
   144  	ContinuationToken     string `xml:"ContinuationToken,omitempty"`
   145  	NextContinuationToken string `xml:"NextContinuationToken,omitempty"`
   146  
   147  	KeyCount  int
   148  	MaxKeys   int
   149  	Delimiter string `xml:"Delimiter,omitempty"`
   150  	// A flag that indicates whether or not ListObjects returned all of the results
   151  	// that satisfied the search criteria.
   152  	IsTruncated bool
   153  
   154  	Contents       []Object
   155  	CommonPrefixes []CommonPrefix
   156  
   157  	// Encoding type used to encode object keys in the response.
   158  	EncodingType string `xml:"EncodingType,omitempty"`
   159  }
   160  
   161  // Part container for part metadata.
   162  type Part struct {
   163  	PartNumber   int
   164  	LastModified string
   165  	ETag         string
   166  	Size         int64
   167  
   168  	// Checksum values
   169  	ChecksumCRC32  string `xml:"ChecksumCRC32,omitempty"`
   170  	ChecksumCRC32C string `xml:"ChecksumCRC32C,omitempty"`
   171  	ChecksumSHA1   string `xml:"ChecksumSHA1,omitempty"`
   172  	ChecksumSHA256 string `xml:"ChecksumSHA256,omitempty"`
   173  }
   174  
   175  // ListPartsResponse - format for list parts response.
   176  type ListPartsResponse struct {
   177  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListPartsResult" json:"-"`
   178  
   179  	Bucket   string
   180  	Key      string
   181  	UploadID string `xml:"UploadId"`
   182  
   183  	Initiator Initiator
   184  	Owner     Owner
   185  
   186  	// The class of storage used to store the object.
   187  	StorageClass string
   188  
   189  	PartNumberMarker     int
   190  	NextPartNumberMarker int
   191  	MaxParts             int
   192  	IsTruncated          bool
   193  
   194  	ChecksumAlgorithm string
   195  	// List of parts.
   196  	Parts []Part `xml:"Part"`
   197  }
   198  
   199  // ListMultipartUploadsResponse - format for list multipart uploads response.
   200  type ListMultipartUploadsResponse struct {
   201  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListMultipartUploadsResult" json:"-"`
   202  
   203  	Bucket             string
   204  	KeyMarker          string
   205  	UploadIDMarker     string `xml:"UploadIdMarker"`
   206  	NextKeyMarker      string
   207  	NextUploadIDMarker string `xml:"NextUploadIdMarker"`
   208  	Delimiter          string `xml:"Delimiter,omitempty"`
   209  	Prefix             string
   210  	EncodingType       string `xml:"EncodingType,omitempty"`
   211  	MaxUploads         int
   212  	IsTruncated        bool
   213  
   214  	// List of pending uploads.
   215  	Uploads []Upload `xml:"Upload"`
   216  
   217  	// Delimed common prefixes.
   218  	CommonPrefixes []CommonPrefix
   219  }
   220  
   221  // ListBucketsResponse - format for list buckets response
   222  type ListBucketsResponse struct {
   223  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListAllMyBucketsResult" json:"-"`
   224  
   225  	Owner Owner
   226  
   227  	// Container for one or more buckets.
   228  	Buckets struct {
   229  		Buckets []Bucket `xml:"Bucket"`
   230  	} // Buckets are nested
   231  }
   232  
   233  // Upload container for in progress multipart upload
   234  type Upload struct {
   235  	Key          string
   236  	UploadID     string `xml:"UploadId"`
   237  	Initiator    Initiator
   238  	Owner        Owner
   239  	StorageClass string
   240  	Initiated    string
   241  }
   242  
   243  // CommonPrefix container for prefix response in ListObjectsResponse
   244  type CommonPrefix struct {
   245  	Prefix string
   246  }
   247  
   248  // Bucket container for bucket metadata
   249  type Bucket struct {
   250  	Name         string
   251  	CreationDate string // time string of format "2006-01-02T15:04:05.000Z"
   252  }
   253  
   254  // ObjectVersion container for object version metadata
   255  type ObjectVersion struct {
   256  	Object
   257  	IsLatest  bool
   258  	VersionID string `xml:"VersionId"`
   259  
   260  	isDeleteMarker bool
   261  }
   262  
   263  // MarshalXML - marshal ObjectVersion
   264  func (o ObjectVersion) MarshalXML(e *xxml.Encoder, start xxml.StartElement) error {
   265  	if o.isDeleteMarker {
   266  		start.Name.Local = "DeleteMarker"
   267  	} else {
   268  		start.Name.Local = "Version"
   269  	}
   270  	type objectVersionWrapper ObjectVersion
   271  	return e.EncodeElement(objectVersionWrapper(o), start)
   272  }
   273  
   274  // DeleteMarkerVersion container for delete marker metadata
   275  type DeleteMarkerVersion struct {
   276  	Key          string
   277  	LastModified string // time string of format "2006-01-02T15:04:05.000Z"
   278  
   279  	// Owner of the object.
   280  	Owner Owner
   281  
   282  	IsLatest  bool
   283  	VersionID string `xml:"VersionId"`
   284  }
   285  
   286  // Metadata metadata items implemented to ensure XML marshaling works.
   287  type Metadata struct {
   288  	Items []struct {
   289  		Key   string
   290  		Value string
   291  	}
   292  }
   293  
   294  // Set add items, duplicate items get replaced.
   295  func (s *Metadata) Set(k, v string) {
   296  	for i, item := range s.Items {
   297  		if item.Key == k {
   298  			s.Items[i] = struct {
   299  				Key   string
   300  				Value string
   301  			}{
   302  				Key:   k,
   303  				Value: v,
   304  			}
   305  			return
   306  		}
   307  	}
   308  	s.Items = append(s.Items, struct {
   309  		Key   string
   310  		Value string
   311  	}{
   312  		Key:   k,
   313  		Value: v,
   314  	})
   315  }
   316  
   317  type xmlKeyEntry struct {
   318  	XMLName xxml.Name
   319  	Value   string `xml:",chardata"`
   320  }
   321  
   322  // MarshalXML - StringMap marshals into XML.
   323  func (s *Metadata) MarshalXML(e *xxml.Encoder, start xxml.StartElement) error {
   324  	if s == nil {
   325  		return nil
   326  	}
   327  
   328  	if len(s.Items) == 0 {
   329  		return nil
   330  	}
   331  
   332  	if err := e.EncodeToken(start); err != nil {
   333  		return err
   334  	}
   335  
   336  	for _, item := range s.Items {
   337  		if err := e.Encode(xmlKeyEntry{
   338  			XMLName: xxml.Name{Local: item.Key},
   339  			Value:   item.Value,
   340  		}); err != nil {
   341  			return err
   342  		}
   343  	}
   344  
   345  	return e.EncodeToken(start.End())
   346  }
   347  
   348  // ObjectInternalInfo contains some internal information about a given
   349  // object, it will printed in listing calls with enabled metadata.
   350  type ObjectInternalInfo struct {
   351  	K int // Data blocks
   352  	M int // Parity blocks
   353  }
   354  
   355  // Object container for object metadata
   356  type Object struct {
   357  	Key          string
   358  	LastModified string // time string of format "2006-01-02T15:04:05.000Z"
   359  	ETag         string
   360  	Size         int64
   361  
   362  	// Owner of the object.
   363  	Owner *Owner `xml:"Owner,omitempty"`
   364  
   365  	// The class of storage used to store the object.
   366  	StorageClass string
   367  
   368  	// UserMetadata user-defined metadata
   369  	UserMetadata *Metadata `xml:"UserMetadata,omitempty"`
   370  	UserTags     string    `xml:"UserTags,omitempty"`
   371  
   372  	Internal *ObjectInternalInfo `xml:"Internal,omitempty"`
   373  }
   374  
   375  // CopyObjectResponse container returns ETag and LastModified of the successfully copied object
   376  type CopyObjectResponse struct {
   377  	XMLName      xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CopyObjectResult" json:"-"`
   378  	LastModified string   // time string of format "2006-01-02T15:04:05.000Z"
   379  	ETag         string   // md5sum of the copied object.
   380  }
   381  
   382  // CopyObjectPartResponse container returns ETag and LastModified of the successfully copied object
   383  type CopyObjectPartResponse struct {
   384  	XMLName      xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CopyPartResult" json:"-"`
   385  	LastModified string   // time string of format "2006-01-02T15:04:05.000Z"
   386  	ETag         string   // md5sum of the copied object part.
   387  }
   388  
   389  // Initiator inherit from Owner struct, fields are same
   390  type Initiator Owner
   391  
   392  // Owner - bucket owner/principal
   393  type Owner struct {
   394  	ID          string
   395  	DisplayName string
   396  }
   397  
   398  // InitiateMultipartUploadResponse container for InitiateMultiPartUpload response, provides uploadID to start MultiPart upload
   399  type InitiateMultipartUploadResponse struct {
   400  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ InitiateMultipartUploadResult" json:"-"`
   401  
   402  	Bucket   string
   403  	Key      string
   404  	UploadID string `xml:"UploadId"`
   405  }
   406  
   407  // CompleteMultipartUploadResponse container for completed multipart upload response
   408  type CompleteMultipartUploadResponse struct {
   409  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CompleteMultipartUploadResult" json:"-"`
   410  
   411  	Location string
   412  	Bucket   string
   413  	Key      string
   414  	ETag     string
   415  
   416  	ChecksumCRC32  string `xml:"ChecksumCRC32,omitempty"`
   417  	ChecksumCRC32C string `xml:"ChecksumCRC32C,omitempty"`
   418  	ChecksumSHA1   string `xml:"ChecksumSHA1,omitempty"`
   419  	ChecksumSHA256 string `xml:"ChecksumSHA256,omitempty"`
   420  }
   421  
   422  // DeleteError structure.
   423  type DeleteError struct {
   424  	Code      string
   425  	Message   string
   426  	Key       string
   427  	VersionID string `xml:"VersionId"`
   428  }
   429  
   430  // DeleteObjectsResponse container for multiple object deletes.
   431  type DeleteObjectsResponse struct {
   432  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ DeleteResult" json:"-"`
   433  
   434  	// Collection of all deleted objects
   435  	DeletedObjects []DeletedObject `xml:"Deleted,omitempty"`
   436  
   437  	// Collection of errors deleting certain objects.
   438  	Errors []DeleteError `xml:"Error,omitempty"`
   439  }
   440  
   441  // PostResponse container for POST object request when success_action_status is set to 201
   442  type PostResponse struct {
   443  	Bucket   string
   444  	Key      string
   445  	ETag     string
   446  	Location string
   447  }
   448  
   449  // returns "https" if the tls boolean is true, "http" otherwise.
   450  func getURLScheme(tls bool) string {
   451  	if tls {
   452  		return httpsScheme
   453  	}
   454  	return httpScheme
   455  }
   456  
   457  // getObjectLocation gets the fully qualified URL of an object.
   458  func getObjectLocation(r *http.Request, domains []string, bucket, object string) string {
   459  	// unit tests do not have host set.
   460  	if r.Host == "" {
   461  		return path.Clean(r.URL.Path)
   462  	}
   463  	proto := handlers.GetSourceScheme(r)
   464  	if proto == "" {
   465  		proto = getURLScheme(globalIsTLS)
   466  	}
   467  	u := &url.URL{
   468  		Host:   r.Host,
   469  		Path:   path.Join(SlashSeparator, bucket, object),
   470  		Scheme: proto,
   471  	}
   472  	// If domain is set then we need to use bucket DNS style.
   473  	for _, domain := range domains {
   474  		if strings.HasPrefix(r.Host, bucket+"."+domain) {
   475  			u.Path = path.Join(SlashSeparator, object)
   476  			break
   477  		}
   478  	}
   479  	return u.String()
   480  }
   481  
   482  // generates ListBucketsResponse from array of BucketInfo which can be
   483  // serialized to match XML and JSON API spec output.
   484  func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
   485  	listbuckets := make([]Bucket, 0, len(buckets))
   486  	data := ListBucketsResponse{}
   487  	owner := Owner{
   488  		ID:          globalMinioDefaultOwnerID,
   489  		DisplayName: "minio",
   490  	}
   491  
   492  	for _, bucket := range buckets {
   493  		listbuckets = append(listbuckets, Bucket{
   494  			Name:         bucket.Name,
   495  			CreationDate: amztime.ISO8601Format(bucket.Created.UTC()),
   496  		})
   497  	}
   498  
   499  	data.Owner = owner
   500  	data.Buckets.Buckets = listbuckets
   501  
   502  	return data
   503  }
   504  
   505  func cleanReservedKeys(metadata map[string]string) map[string]string {
   506  	m := cloneMSS(metadata)
   507  
   508  	switch kind, _ := crypto.IsEncrypted(metadata); kind {
   509  	case crypto.S3:
   510  		m[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionAES
   511  	case crypto.S3KMS:
   512  		m[xhttp.AmzServerSideEncryption] = xhttp.AmzEncryptionKMS
   513  		m[xhttp.AmzServerSideEncryptionKmsID] = kmsKeyIDFromMetadata(metadata)
   514  		if kmsCtx, ok := metadata[crypto.MetaContext]; ok {
   515  			m[xhttp.AmzServerSideEncryptionKmsContext] = kmsCtx
   516  		}
   517  	case crypto.SSEC:
   518  		m[xhttp.AmzServerSideEncryptionCustomerAlgorithm] = xhttp.AmzEncryptionAES
   519  
   520  	}
   521  
   522  	var toRemove []string
   523  	for k := range cleanMinioInternalMetadataKeys(m) {
   524  		if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
   525  			// Do not need to send any internal metadata
   526  			// values to client.
   527  			toRemove = append(toRemove, k)
   528  			continue
   529  		}
   530  
   531  		// https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
   532  		if equals(k, xhttp.AmzMetaUnencryptedContentLength, xhttp.AmzMetaUnencryptedContentMD5) {
   533  			toRemove = append(toRemove, k)
   534  			continue
   535  		}
   536  	}
   537  
   538  	for _, k := range toRemove {
   539  		delete(m, k)
   540  		delete(m, strings.ToLower(k))
   541  	}
   542  
   543  	return m
   544  }
   545  
   546  // generates an ListBucketVersions response for the said bucket with other enumerated options.
   547  func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo, metadata metaCheckFn) ListVersionsResponse {
   548  	versions := make([]ObjectVersion, 0, len(resp.Objects))
   549  
   550  	owner := &Owner{
   551  		ID:          globalMinioDefaultOwnerID,
   552  		DisplayName: "minio",
   553  	}
   554  	data := ListVersionsResponse{}
   555  	var lastObjMetaName string
   556  	var tagErr, metaErr APIErrorCode = -1, -1
   557  
   558  	for _, object := range resp.Objects {
   559  		if object.Name == "" {
   560  			continue
   561  		}
   562  		// Cache checks for the same object
   563  		if metadata != nil && lastObjMetaName != object.Name {
   564  			tagErr = metadata(object.Name, policy.GetObjectTaggingAction)
   565  			metaErr = metadata(object.Name, policy.GetObjectAction)
   566  			lastObjMetaName = object.Name
   567  		}
   568  		content := ObjectVersion{}
   569  		content.Key = s3EncodeName(object.Name, encodingType)
   570  		content.LastModified = amztime.ISO8601Format(object.ModTime.UTC())
   571  		if object.ETag != "" {
   572  			content.ETag = "\"" + object.ETag + "\""
   573  		}
   574  		content.Size = object.Size
   575  		if object.StorageClass != "" {
   576  			content.StorageClass = object.StorageClass
   577  		} else {
   578  			content.StorageClass = globalMinioDefaultStorageClass
   579  		}
   580  		if tagErr == ErrNone {
   581  			content.UserTags = object.UserTags
   582  		}
   583  		if metaErr == ErrNone {
   584  			content.UserMetadata = &Metadata{}
   585  			switch kind, _ := crypto.IsEncrypted(object.UserDefined); kind {
   586  			case crypto.S3:
   587  				content.UserMetadata.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
   588  			case crypto.S3KMS:
   589  				content.UserMetadata.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS)
   590  			case crypto.SSEC:
   591  				content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
   592  			}
   593  			for k, v := range cleanReservedKeys(object.UserDefined) {
   594  				content.UserMetadata.Set(k, v)
   595  			}
   596  
   597  			content.UserMetadata.Set("expires", object.Expires.Format(http.TimeFormat))
   598  			content.Internal = &ObjectInternalInfo{
   599  				K: object.DataBlocks,
   600  				M: object.ParityBlocks,
   601  			}
   602  		}
   603  		content.Owner = owner
   604  		content.VersionID = object.VersionID
   605  		if content.VersionID == "" {
   606  			content.VersionID = nullVersionID
   607  		}
   608  		content.IsLatest = object.IsLatest
   609  		content.isDeleteMarker = object.DeleteMarker
   610  		versions = append(versions, content)
   611  	}
   612  
   613  	data.Name = bucket
   614  	data.Versions = versions
   615  	data.EncodingType = encodingType
   616  	data.Prefix = s3EncodeName(prefix, encodingType)
   617  	data.KeyMarker = s3EncodeName(marker, encodingType)
   618  	data.Delimiter = s3EncodeName(delimiter, encodingType)
   619  	data.MaxKeys = maxKeys
   620  
   621  	data.NextKeyMarker = s3EncodeName(resp.NextMarker, encodingType)
   622  	data.NextVersionIDMarker = resp.NextVersionIDMarker
   623  	data.VersionIDMarker = versionIDMarker
   624  	data.IsTruncated = resp.IsTruncated
   625  
   626  	prefixes := make([]CommonPrefix, 0, len(resp.Prefixes))
   627  	for _, prefix := range resp.Prefixes {
   628  		prefixItem := CommonPrefix{}
   629  		prefixItem.Prefix = s3EncodeName(prefix, encodingType)
   630  		prefixes = append(prefixes, prefixItem)
   631  	}
   632  	data.CommonPrefixes = prefixes
   633  	return data
   634  }
   635  
   636  // generates an ListObjectsV1 response for the said bucket with other enumerated options.
   637  func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
   638  	contents := make([]Object, 0, len(resp.Objects))
   639  	owner := &Owner{
   640  		ID:          globalMinioDefaultOwnerID,
   641  		DisplayName: "minio",
   642  	}
   643  	data := ListObjectsResponse{}
   644  
   645  	for _, object := range resp.Objects {
   646  		content := Object{}
   647  		if object.Name == "" {
   648  			continue
   649  		}
   650  		content.Key = s3EncodeName(object.Name, encodingType)
   651  		content.LastModified = amztime.ISO8601Format(object.ModTime.UTC())
   652  		if object.ETag != "" {
   653  			content.ETag = "\"" + object.ETag + "\""
   654  		}
   655  		content.Size = object.Size
   656  		if object.StorageClass != "" {
   657  			content.StorageClass = object.StorageClass
   658  		} else {
   659  			content.StorageClass = globalMinioDefaultStorageClass
   660  		}
   661  		content.Owner = owner
   662  		contents = append(contents, content)
   663  	}
   664  	data.Name = bucket
   665  	data.Contents = contents
   666  
   667  	data.EncodingType = encodingType
   668  	data.Prefix = s3EncodeName(prefix, encodingType)
   669  	data.Marker = s3EncodeName(marker, encodingType)
   670  	data.Delimiter = s3EncodeName(delimiter, encodingType)
   671  	data.MaxKeys = maxKeys
   672  	data.NextMarker = s3EncodeName(resp.NextMarker, encodingType)
   673  	data.IsTruncated = resp.IsTruncated
   674  
   675  	prefixes := make([]CommonPrefix, 0, len(resp.Prefixes))
   676  	for _, prefix := range resp.Prefixes {
   677  		prefixItem := CommonPrefix{}
   678  		prefixItem.Prefix = s3EncodeName(prefix, encodingType)
   679  		prefixes = append(prefixes, prefixItem)
   680  	}
   681  	data.CommonPrefixes = prefixes
   682  	return data
   683  }
   684  
   685  // generates an ListObjectsV2 response for the said bucket with other enumerated options.
   686  func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter, delimiter, encodingType string, fetchOwner, isTruncated bool, maxKeys int, objects []ObjectInfo, prefixes []string, metadata metaCheckFn) ListObjectsV2Response {
   687  	contents := make([]Object, 0, len(objects))
   688  	var owner *Owner
   689  	if fetchOwner {
   690  		owner = &Owner{
   691  			ID:          globalMinioDefaultOwnerID,
   692  			DisplayName: "minio",
   693  		}
   694  	}
   695  
   696  	data := ListObjectsV2Response{}
   697  
   698  	for _, object := range objects {
   699  		content := Object{}
   700  		if object.Name == "" {
   701  			continue
   702  		}
   703  		content.Key = s3EncodeName(object.Name, encodingType)
   704  		content.LastModified = amztime.ISO8601Format(object.ModTime.UTC())
   705  		if object.ETag != "" {
   706  			content.ETag = "\"" + object.ETag + "\""
   707  		}
   708  		content.Size = object.Size
   709  		if object.StorageClass != "" {
   710  			content.StorageClass = object.StorageClass
   711  		} else {
   712  			content.StorageClass = globalMinioDefaultStorageClass
   713  		}
   714  		content.Owner = owner
   715  		if metadata != nil {
   716  			if metadata(object.Name, policy.GetObjectTaggingAction) == ErrNone {
   717  				content.UserTags = object.UserTags
   718  			}
   719  			if metadata(object.Name, policy.GetObjectAction) == ErrNone {
   720  				content.UserMetadata = &Metadata{}
   721  				switch kind, _ := crypto.IsEncrypted(object.UserDefined); kind {
   722  				case crypto.S3:
   723  					content.UserMetadata.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
   724  				case crypto.S3KMS:
   725  					content.UserMetadata.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS)
   726  				case crypto.SSEC:
   727  					content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
   728  				}
   729  				for k, v := range cleanReservedKeys(object.UserDefined) {
   730  					content.UserMetadata.Set(k, v)
   731  				}
   732  				content.UserMetadata.Set("expires", object.Expires.Format(http.TimeFormat))
   733  				content.Internal = &ObjectInternalInfo{
   734  					K: object.DataBlocks,
   735  					M: object.ParityBlocks,
   736  				}
   737  			}
   738  		}
   739  		contents = append(contents, content)
   740  	}
   741  	data.Name = bucket
   742  	data.Contents = contents
   743  
   744  	data.EncodingType = encodingType
   745  	data.StartAfter = s3EncodeName(startAfter, encodingType)
   746  	data.Delimiter = s3EncodeName(delimiter, encodingType)
   747  	data.Prefix = s3EncodeName(prefix, encodingType)
   748  	data.MaxKeys = maxKeys
   749  	data.ContinuationToken = base64.StdEncoding.EncodeToString([]byte(token))
   750  	data.NextContinuationToken = base64.StdEncoding.EncodeToString([]byte(nextToken))
   751  	data.IsTruncated = isTruncated
   752  
   753  	commonPrefixes := make([]CommonPrefix, 0, len(prefixes))
   754  	for _, prefix := range prefixes {
   755  		prefixItem := CommonPrefix{}
   756  		prefixItem.Prefix = s3EncodeName(prefix, encodingType)
   757  		commonPrefixes = append(commonPrefixes, prefixItem)
   758  	}
   759  	data.CommonPrefixes = commonPrefixes
   760  	data.KeyCount = len(data.Contents) + len(data.CommonPrefixes)
   761  	return data
   762  }
   763  
   764  type metaCheckFn = func(name string, action policy.Action) (s3Err APIErrorCode)
   765  
   766  // generates CopyObjectResponse from etag and lastModified time.
   767  func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectResponse {
   768  	return CopyObjectResponse{
   769  		ETag:         "\"" + etag + "\"",
   770  		LastModified: amztime.ISO8601Format(lastModified.UTC()),
   771  	}
   772  }
   773  
   774  // generates CopyObjectPartResponse from etag and lastModified time.
   775  func generateCopyObjectPartResponse(etag string, lastModified time.Time) CopyObjectPartResponse {
   776  	return CopyObjectPartResponse{
   777  		ETag:         "\"" + etag + "\"",
   778  		LastModified: amztime.ISO8601Format(lastModified.UTC()),
   779  	}
   780  }
   781  
   782  // generates InitiateMultipartUploadResponse for given bucket, key and uploadID.
   783  func generateInitiateMultipartUploadResponse(bucket, key, uploadID string) InitiateMultipartUploadResponse {
   784  	return InitiateMultipartUploadResponse{
   785  		Bucket:   bucket,
   786  		Key:      key,
   787  		UploadID: uploadID,
   788  	}
   789  }
   790  
   791  // generates CompleteMultipartUploadResponse for given bucket, key, location and ETag.
   792  func generateCompleteMultpartUploadResponse(bucket, key, location string, oi ObjectInfo) CompleteMultipartUploadResponse {
   793  	cs := oi.decryptChecksums(0)
   794  	c := CompleteMultipartUploadResponse{
   795  		Location: location,
   796  		Bucket:   bucket,
   797  		Key:      key,
   798  		// AWS S3 quotes the ETag in XML, make sure we are compatible here.
   799  		ETag:           "\"" + oi.ETag + "\"",
   800  		ChecksumSHA1:   cs[hash.ChecksumSHA1.String()],
   801  		ChecksumSHA256: cs[hash.ChecksumSHA256.String()],
   802  		ChecksumCRC32:  cs[hash.ChecksumCRC32.String()],
   803  		ChecksumCRC32C: cs[hash.ChecksumCRC32C.String()],
   804  	}
   805  	return c
   806  }
   807  
   808  // generates ListPartsResponse from ListPartsInfo.
   809  func generateListPartsResponse(partsInfo ListPartsInfo, encodingType string) ListPartsResponse {
   810  	listPartsResponse := ListPartsResponse{}
   811  	listPartsResponse.Bucket = partsInfo.Bucket
   812  	listPartsResponse.Key = s3EncodeName(partsInfo.Object, encodingType)
   813  	listPartsResponse.UploadID = partsInfo.UploadID
   814  	listPartsResponse.StorageClass = globalMinioDefaultStorageClass
   815  
   816  	// Dumb values not meaningful
   817  	listPartsResponse.Initiator = Initiator{
   818  		ID:          globalMinioDefaultOwnerID,
   819  		DisplayName: globalMinioDefaultOwnerID,
   820  	}
   821  	listPartsResponse.Owner = Owner{
   822  		ID:          globalMinioDefaultOwnerID,
   823  		DisplayName: globalMinioDefaultOwnerID,
   824  	}
   825  
   826  	listPartsResponse.MaxParts = partsInfo.MaxParts
   827  	listPartsResponse.PartNumberMarker = partsInfo.PartNumberMarker
   828  	listPartsResponse.IsTruncated = partsInfo.IsTruncated
   829  	listPartsResponse.NextPartNumberMarker = partsInfo.NextPartNumberMarker
   830  	listPartsResponse.ChecksumAlgorithm = partsInfo.ChecksumAlgorithm
   831  
   832  	listPartsResponse.Parts = make([]Part, len(partsInfo.Parts))
   833  	for index, part := range partsInfo.Parts {
   834  		newPart := Part{}
   835  		newPart.PartNumber = part.PartNumber
   836  		newPart.ETag = "\"" + part.ETag + "\""
   837  		newPart.Size = part.Size
   838  		newPart.LastModified = amztime.ISO8601Format(part.LastModified.UTC())
   839  		newPart.ChecksumCRC32 = part.ChecksumCRC32
   840  		newPart.ChecksumCRC32C = part.ChecksumCRC32C
   841  		newPart.ChecksumSHA1 = part.ChecksumSHA1
   842  		newPart.ChecksumSHA256 = part.ChecksumSHA256
   843  		listPartsResponse.Parts[index] = newPart
   844  	}
   845  	return listPartsResponse
   846  }
   847  
   848  // generates ListMultipartUploadsResponse for given bucket and ListMultipartsInfo.
   849  func generateListMultipartUploadsResponse(bucket string, multipartsInfo ListMultipartsInfo, encodingType string) ListMultipartUploadsResponse {
   850  	listMultipartUploadsResponse := ListMultipartUploadsResponse{}
   851  	listMultipartUploadsResponse.Bucket = bucket
   852  	listMultipartUploadsResponse.Delimiter = s3EncodeName(multipartsInfo.Delimiter, encodingType)
   853  	listMultipartUploadsResponse.IsTruncated = multipartsInfo.IsTruncated
   854  	listMultipartUploadsResponse.EncodingType = encodingType
   855  	listMultipartUploadsResponse.Prefix = s3EncodeName(multipartsInfo.Prefix, encodingType)
   856  	listMultipartUploadsResponse.KeyMarker = s3EncodeName(multipartsInfo.KeyMarker, encodingType)
   857  	listMultipartUploadsResponse.NextKeyMarker = s3EncodeName(multipartsInfo.NextKeyMarker, encodingType)
   858  	listMultipartUploadsResponse.MaxUploads = multipartsInfo.MaxUploads
   859  	listMultipartUploadsResponse.NextUploadIDMarker = multipartsInfo.NextUploadIDMarker
   860  	listMultipartUploadsResponse.UploadIDMarker = multipartsInfo.UploadIDMarker
   861  	listMultipartUploadsResponse.CommonPrefixes = make([]CommonPrefix, len(multipartsInfo.CommonPrefixes))
   862  	for index, commonPrefix := range multipartsInfo.CommonPrefixes {
   863  		listMultipartUploadsResponse.CommonPrefixes[index] = CommonPrefix{
   864  			Prefix: s3EncodeName(commonPrefix, encodingType),
   865  		}
   866  	}
   867  	listMultipartUploadsResponse.Uploads = make([]Upload, len(multipartsInfo.Uploads))
   868  	for index, upload := range multipartsInfo.Uploads {
   869  		newUpload := Upload{}
   870  		newUpload.UploadID = upload.UploadID
   871  		newUpload.Key = s3EncodeName(upload.Object, encodingType)
   872  		newUpload.Initiated = amztime.ISO8601Format(upload.Initiated.UTC())
   873  		listMultipartUploadsResponse.Uploads[index] = newUpload
   874  	}
   875  	return listMultipartUploadsResponse
   876  }
   877  
   878  // generate multi objects delete response.
   879  func generateMultiDeleteResponse(quiet bool, deletedObjects []DeletedObject, errs []DeleteError) DeleteObjectsResponse {
   880  	deleteResp := DeleteObjectsResponse{}
   881  	if !quiet {
   882  		deleteResp.DeletedObjects = deletedObjects
   883  	}
   884  	deleteResp.Errors = errs
   885  	return deleteResp
   886  }
   887  
   888  func writeResponse(w http.ResponseWriter, statusCode int, response []byte, mType mimeType) {
   889  	if statusCode == 0 {
   890  		statusCode = 200
   891  	}
   892  	// Similar check to http.checkWriteHeaderCode
   893  	if statusCode < 100 || statusCode > 999 {
   894  		logger.LogIf(context.Background(), fmt.Errorf("invalid WriteHeader code %v", statusCode))
   895  		statusCode = http.StatusInternalServerError
   896  	}
   897  	setCommonHeaders(w)
   898  	if mType != mimeNone {
   899  		w.Header().Set(xhttp.ContentType, string(mType))
   900  	}
   901  	w.Header().Set(xhttp.ContentLength, strconv.Itoa(len(response)))
   902  	w.WriteHeader(statusCode)
   903  	if response != nil {
   904  		w.Write(response)
   905  	}
   906  }
   907  
   908  // mimeType represents various MIME type used API responses.
   909  type mimeType string
   910  
   911  const (
   912  	// Means no response type.
   913  	mimeNone mimeType = ""
   914  	// Means response type is JSON.
   915  	mimeJSON mimeType = "application/json"
   916  	// Means response type is XML.
   917  	mimeXML mimeType = "application/xml"
   918  )
   919  
   920  // writeSuccessResponseJSON writes success headers and response if any,
   921  // with content-type set to `application/json`.
   922  func writeSuccessResponseJSON(w http.ResponseWriter, response []byte) {
   923  	writeResponse(w, http.StatusOK, response, mimeJSON)
   924  }
   925  
   926  // writeSuccessResponseXML writes success headers and response if any,
   927  // with content-type set to `application/xml`.
   928  func writeSuccessResponseXML(w http.ResponseWriter, response []byte) {
   929  	writeResponse(w, http.StatusOK, response, mimeXML)
   930  }
   931  
   932  // writeSuccessNoContent writes success headers with http status 204
   933  func writeSuccessNoContent(w http.ResponseWriter) {
   934  	writeResponse(w, http.StatusNoContent, nil, mimeNone)
   935  }
   936  
   937  // writeRedirectSeeOther writes Location header with http status 303
   938  func writeRedirectSeeOther(w http.ResponseWriter, location string) {
   939  	w.Header().Set(xhttp.Location, location)
   940  	writeResponse(w, http.StatusSeeOther, nil, mimeNone)
   941  }
   942  
   943  func writeSuccessResponseHeadersOnly(w http.ResponseWriter) {
   944  	writeResponse(w, http.StatusOK, nil, mimeNone)
   945  }
   946  
   947  // writeErrorResponse writes error headers
   948  func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
   949  	if err.HTTPStatusCode == http.StatusServiceUnavailable {
   950  		// Set retry-after header to indicate user-agents to retry request after 120secs.
   951  		// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
   952  		w.Header().Set(xhttp.RetryAfter, "120")
   953  	}
   954  
   955  	switch err.Code {
   956  	case "InvalidRegion":
   957  		err.Description = fmt.Sprintf("Region does not match; expecting '%s'.", globalSite.Region)
   958  	case "AuthorizationHeaderMalformed":
   959  		err.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region)
   960  	}
   961  
   962  	// Similar check to http.checkWriteHeaderCode
   963  	if err.HTTPStatusCode < 100 || err.HTTPStatusCode > 999 {
   964  		logger.LogIf(ctx, fmt.Errorf("invalid WriteHeader code %v from %v", err.HTTPStatusCode, err.Code))
   965  		err.HTTPStatusCode = http.StatusInternalServerError
   966  	}
   967  
   968  	// Generate error response.
   969  	errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path,
   970  		w.Header().Get(xhttp.AmzRequestID), w.Header().Get(xhttp.AmzRequestHostID))
   971  	encodedErrorResponse := encodeResponse(errorResponse)
   972  	writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML)
   973  }
   974  
   975  func writeErrorResponseHeadersOnly(w http.ResponseWriter, err APIError) {
   976  	w.Header().Set(xMinIOErrCodeHeader, err.Code)
   977  	w.Header().Set(xMinIOErrDescHeader, "\""+err.Description+"\"")
   978  	writeResponse(w, err.HTTPStatusCode, nil, mimeNone)
   979  }
   980  
   981  func writeErrorResponseString(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
   982  	// Generate string error response.
   983  	writeResponse(w, err.HTTPStatusCode, []byte(err.Description), mimeNone)
   984  }
   985  
   986  // writeErrorResponseJSON - writes error response in JSON format;
   987  // useful for admin APIs.
   988  func writeErrorResponseJSON(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
   989  	// Generate error response.
   990  	errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path, w.Header().Get(xhttp.AmzRequestID), w.Header().Get(xhttp.AmzRequestHostID))
   991  	encodedErrorResponse := encodeResponseJSON(errorResponse)
   992  	writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
   993  }
   994  
   995  // writeCustomErrorResponseJSON - similar to writeErrorResponseJSON,
   996  // but accepts the error message directly (this allows messages to be
   997  // dynamically generated.)
   998  func writeCustomErrorResponseJSON(ctx context.Context, w http.ResponseWriter, err APIError,
   999  	errBody string, reqURL *url.URL,
  1000  ) {
  1001  	reqInfo := logger.GetReqInfo(ctx)
  1002  	errorResponse := APIErrorResponse{
  1003  		Code:       err.Code,
  1004  		Message:    errBody,
  1005  		Resource:   reqURL.Path,
  1006  		BucketName: reqInfo.BucketName,
  1007  		Key:        reqInfo.ObjectName,
  1008  		RequestID:  w.Header().Get(xhttp.AmzRequestID),
  1009  		HostID:     globalDeploymentID(),
  1010  	}
  1011  	encodedErrorResponse := encodeResponseJSON(errorResponse)
  1012  	writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
  1013  }