storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/api-response.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2015, 2016, 2017, 2018 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package cmd
    18  
    19  import (
    20  	"context"
    21  	"encoding/base64"
    22  	"encoding/xml"
    23  	"fmt"
    24  	"net/http"
    25  	"net/url"
    26  	"path"
    27  	"strconv"
    28  	"strings"
    29  	"time"
    30  
    31  	xhttp "storj.io/minio/cmd/http"
    32  	"storj.io/minio/cmd/logger"
    33  	"storj.io/minio/pkg/handlers"
    34  )
    35  
    36  const (
    37  	// RFC3339 a subset of the ISO8601 timestamp format. e.g 2014-04-29T18:30:38Z
    38  	iso8601TimeFormat = "2006-01-02T15:04:05.000Z" // Reply date format with nanosecond precision.
    39  	maxObjectList     = 1000                       // Limit number of objects in a listObjectsResponse/listObjectsVersionsResponse.
    40  	maxDeleteList     = 10000                      // Limit number of objects deleted in a delete call.
    41  	maxUploadsList    = 10000                      // Limit number of uploads in a listUploadsResponse.
    42  	maxPartsList      = 10000                      // Limit number of parts in a listPartsResponse.
    43  )
    44  
    45  // LocationResponse - format for location response.
    46  type LocationResponse struct {
    47  	XMLName  xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ LocationConstraint" json:"-"`
    48  	Location string   `xml:",chardata"`
    49  }
    50  
    51  // PolicyStatus captures information returned by GetBucketPolicyStatusHandler
    52  type PolicyStatus struct {
    53  	XMLName  xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ PolicyStatus" json:"-"`
    54  	IsPublic string
    55  }
    56  
    57  // ListVersionsResponse - format for list bucket versions response.
    58  type ListVersionsResponse struct {
    59  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListVersionsResult" json:"-"`
    60  
    61  	Name      string
    62  	Prefix    string
    63  	KeyMarker string
    64  
    65  	// When response is truncated (the IsTruncated element value in the response
    66  	// is true), you can use the key name in this field as marker in the subsequent
    67  	// request to get next set of objects. Server lists objects in alphabetical
    68  	// order Note: This element is returned only if you have delimiter request parameter
    69  	// specified. If response does not include the NextMaker and it is truncated,
    70  	// you can use the value of the last Key in the response as the marker in the
    71  	// subsequent request to get the next set of object keys.
    72  	NextKeyMarker string `xml:"NextKeyMarker,omitempty"`
    73  
    74  	// When the number of responses exceeds the value of MaxKeys,
    75  	// NextVersionIdMarker specifies the first object version not
    76  	// returned that satisfies the search criteria. Use this value
    77  	// for the version-id-marker request parameter in a subsequent request.
    78  	NextVersionIDMarker string `xml:"NextVersionIdMarker"`
    79  
    80  	// Marks the last version of the Key returned in a truncated response.
    81  	VersionIDMarker string `xml:"VersionIdMarker"`
    82  
    83  	MaxKeys   int
    84  	Delimiter string
    85  	// A flag that indicates whether or not ListObjects returned all of the results
    86  	// that satisfied the search criteria.
    87  	IsTruncated bool
    88  
    89  	CommonPrefixes []CommonPrefix
    90  	Versions       []ObjectVersion
    91  
    92  	// Encoding type used to encode object keys in the response.
    93  	EncodingType string `xml:"EncodingType,omitempty"`
    94  }
    95  
    96  // ListObjectsResponse - format for list objects response.
    97  type ListObjectsResponse struct {
    98  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListBucketResult" json:"-"`
    99  
   100  	Name   string
   101  	Prefix string
   102  	Marker string
   103  
   104  	// When response is truncated (the IsTruncated element value in the response
   105  	// is true), you can use the key name in this field as marker in the subsequent
   106  	// request to get next set of objects. Server lists objects in alphabetical
   107  	// order Note: This element is returned only if you have delimiter request parameter
   108  	// specified. If response does not include the NextMaker and it is truncated,
   109  	// you can use the value of the last Key in the response as the marker in the
   110  	// subsequent request to get the next set of object keys.
   111  	NextMarker string `xml:"NextMarker,omitempty"`
   112  
   113  	MaxKeys   int
   114  	Delimiter string
   115  	// A flag that indicates whether or not ListObjects returned all of the results
   116  	// that satisfied the search criteria.
   117  	IsTruncated bool
   118  
   119  	Contents       []Object
   120  	CommonPrefixes []CommonPrefix
   121  
   122  	// Encoding type used to encode object keys in the response.
   123  	EncodingType string `xml:"EncodingType,omitempty"`
   124  }
   125  
   126  // ListObjectsV2Response - format for list objects response.
   127  type ListObjectsV2Response struct {
   128  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListBucketResult" json:"-"`
   129  
   130  	Name       string
   131  	Prefix     string
   132  	StartAfter string `xml:"StartAfter,omitempty"`
   133  	// When response is truncated (the IsTruncated element value in the response
   134  	// is true), you can use the key name in this field as marker in the subsequent
   135  	// request to get next set of objects. Server lists objects in alphabetical
   136  	// order Note: This element is returned only if you have delimiter request parameter
   137  	// specified. If response does not include the NextMaker and it is truncated,
   138  	// you can use the value of the last Key in the response as the marker in the
   139  	// subsequent request to get the next set of object keys.
   140  	ContinuationToken     string `xml:"ContinuationToken,omitempty"`
   141  	NextContinuationToken string `xml:"NextContinuationToken,omitempty"`
   142  
   143  	KeyCount  int
   144  	MaxKeys   int
   145  	Delimiter string
   146  	// A flag that indicates whether or not ListObjects returned all of the results
   147  	// that satisfied the search criteria.
   148  	IsTruncated bool
   149  
   150  	Contents       []Object
   151  	CommonPrefixes []CommonPrefix
   152  
   153  	// Encoding type used to encode object keys in the response.
   154  	EncodingType string `xml:"EncodingType,omitempty"`
   155  }
   156  
   157  // Part container for part metadata.
   158  type Part struct {
   159  	PartNumber   int
   160  	LastModified string
   161  	ETag         string
   162  	Size         int64
   163  }
   164  
   165  // ListPartsResponse - format for list parts response.
   166  type ListPartsResponse struct {
   167  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListPartsResult" json:"-"`
   168  
   169  	Bucket   string
   170  	Key      string
   171  	UploadID string `xml:"UploadId"`
   172  
   173  	Initiator Initiator
   174  	Owner     Owner
   175  
   176  	// The class of storage used to store the object.
   177  	StorageClass string
   178  
   179  	PartNumberMarker     int
   180  	NextPartNumberMarker int
   181  	MaxParts             int
   182  	IsTruncated          bool
   183  
   184  	// List of parts.
   185  	Parts []Part `xml:"Part"`
   186  }
   187  
   188  // ListMultipartUploadsResponse - format for list multipart uploads response.
   189  type ListMultipartUploadsResponse struct {
   190  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListMultipartUploadsResult" json:"-"`
   191  
   192  	Bucket             string
   193  	KeyMarker          string
   194  	UploadIDMarker     string `xml:"UploadIdMarker"`
   195  	NextKeyMarker      string
   196  	NextUploadIDMarker string `xml:"NextUploadIdMarker"`
   197  	Delimiter          string
   198  	Prefix             string
   199  	EncodingType       string `xml:"EncodingType,omitempty"`
   200  	MaxUploads         int
   201  	IsTruncated        bool
   202  
   203  	// List of pending uploads.
   204  	Uploads []Upload `xml:"Upload"`
   205  
   206  	// Delimed common prefixes.
   207  	CommonPrefixes []CommonPrefix
   208  }
   209  
   210  // ListBucketsResponse - format for list buckets response
   211  type ListBucketsResponse struct {
   212  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListAllMyBucketsResult" json:"-"`
   213  
   214  	Owner Owner
   215  
   216  	// Container for one or more buckets.
   217  	Buckets struct {
   218  		Buckets []Bucket `xml:"Bucket"`
   219  	} // Buckets are nested
   220  }
   221  
   222  // Upload container for in progress multipart upload
   223  type Upload struct {
   224  	Key          string
   225  	UploadID     string `xml:"UploadId"`
   226  	Initiator    Initiator
   227  	Owner        Owner
   228  	StorageClass string
   229  	Initiated    string
   230  }
   231  
   232  // CommonPrefix container for prefix response in ListObjectsResponse
   233  type CommonPrefix struct {
   234  	Prefix string
   235  }
   236  
   237  // Bucket container for bucket metadata
   238  type Bucket struct {
   239  	Name         string
   240  	CreationDate string // time string of format "2006-01-02T15:04:05.000Z"
   241  }
   242  
   243  // ObjectVersion container for object version metadata
   244  type ObjectVersion struct {
   245  	Object
   246  	IsLatest  bool
   247  	VersionID string `xml:"VersionId"`
   248  
   249  	isDeleteMarker bool
   250  }
   251  
   252  // MarshalXML - marshal ObjectVersion
   253  func (o ObjectVersion) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
   254  	if o.isDeleteMarker {
   255  		start.Name.Local = "DeleteMarker"
   256  	} else {
   257  		start.Name.Local = "Version"
   258  	}
   259  
   260  	type objectVersionWrapper ObjectVersion
   261  	return e.EncodeElement(objectVersionWrapper(o), start)
   262  }
   263  
   264  // StringMap is a map[string]string.
   265  type StringMap map[string]string
   266  
   267  // MarshalXML - StringMap marshals into XML.
   268  func (s StringMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
   269  
   270  	tokens := []xml.Token{start}
   271  
   272  	for key, value := range s {
   273  		t := xml.StartElement{}
   274  		t.Name = xml.Name{
   275  			Space: "",
   276  			Local: key,
   277  		}
   278  		tokens = append(tokens, t, xml.CharData(value), xml.EndElement{Name: t.Name})
   279  	}
   280  
   281  	tokens = append(tokens, xml.EndElement{
   282  		Name: start.Name,
   283  	})
   284  
   285  	for _, t := range tokens {
   286  		if err := e.EncodeToken(t); err != nil {
   287  			return err
   288  		}
   289  	}
   290  
   291  	// flush to ensure tokens are written
   292  	return e.Flush()
   293  }
   294  
   295  // Object container for object metadata
   296  type Object struct {
   297  	Key          string
   298  	LastModified string // time string of format "2006-01-02T15:04:05.000Z"
   299  	ETag         string
   300  	Size         int64
   301  
   302  	// Owner of the object.
   303  	Owner Owner
   304  
   305  	// The class of storage used to store the object.
   306  	StorageClass string
   307  
   308  	// UserMetadata user-defined metadata
   309  	UserMetadata StringMap `xml:"UserMetadata,omitempty"`
   310  }
   311  
   312  // CopyObjectResponse container returns ETag and LastModified of the successfully copied object
   313  type CopyObjectResponse struct {
   314  	XMLName      xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CopyObjectResult" json:"-"`
   315  	LastModified string   // time string of format "2006-01-02T15:04:05.000Z"
   316  	ETag         string   // md5sum of the copied object.
   317  }
   318  
   319  // CopyObjectPartResponse container returns ETag and LastModified of the successfully copied object
   320  type CopyObjectPartResponse struct {
   321  	XMLName      xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CopyPartResult" json:"-"`
   322  	LastModified string   // time string of format "2006-01-02T15:04:05.000Z"
   323  	ETag         string   // md5sum of the copied object part.
   324  }
   325  
   326  // Initiator inherit from Owner struct, fields are same
   327  type Initiator Owner
   328  
   329  // Owner - bucket owner/principal
   330  type Owner struct {
   331  	ID          string
   332  	DisplayName string
   333  }
   334  
   335  // InitiateMultipartUploadResponse container for InitiateMultiPartUpload response, provides uploadID to start MultiPart upload
   336  type InitiateMultipartUploadResponse struct {
   337  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ InitiateMultipartUploadResult" json:"-"`
   338  
   339  	Bucket   string
   340  	Key      string
   341  	UploadID string `xml:"UploadId"`
   342  }
   343  
   344  // CompleteMultipartUploadResponse container for completed multipart upload response
   345  type CompleteMultipartUploadResponse struct {
   346  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CompleteMultipartUploadResult" json:"-"`
   347  
   348  	Location string
   349  	Bucket   string
   350  	Key      string
   351  	ETag     string
   352  }
   353  
   354  // DeleteError structure.
   355  type DeleteError struct {
   356  	Code      string
   357  	Message   string
   358  	Key       string
   359  	VersionID string `xml:"VersionId"`
   360  }
   361  
   362  // DeleteObjectsResponse container for multiple object deletes.
   363  type DeleteObjectsResponse struct {
   364  	XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ DeleteResult" json:"-"`
   365  
   366  	// Collection of all deleted objects
   367  	DeletedObjects []DeletedObject `xml:"Deleted,omitempty"`
   368  
   369  	// Collection of errors deleting certain objects.
   370  	Errors []DeleteError `xml:"Error,omitempty"`
   371  }
   372  
   373  // PostResponse container for POST object request when success_action_status is set to 201
   374  type PostResponse struct {
   375  	Bucket   string
   376  	Key      string
   377  	ETag     string
   378  	Location string
   379  }
   380  
   381  // returns "https" if the tls boolean is true, "http" otherwise.
   382  func getURLScheme(tls bool) string {
   383  	if tls {
   384  		return httpsScheme
   385  	}
   386  	return httpScheme
   387  }
   388  
   389  // getObjectLocation gets the fully qualified URL of an object.
   390  func getObjectLocation(r *http.Request, domains []string, bucket, object string) string {
   391  	// unit tests do not have host set.
   392  	if r.Host == "" {
   393  		return path.Clean(r.URL.Path)
   394  	}
   395  	proto := handlers.GetSourceScheme(r)
   396  	if proto == "" {
   397  		proto = getURLScheme(GlobalIsTLS)
   398  	}
   399  	u := &url.URL{
   400  		Host:   r.Host,
   401  		Path:   path.Join(SlashSeparator, bucket, object),
   402  		Scheme: proto,
   403  	}
   404  	// If domain is set then we need to use bucket DNS style.
   405  	for _, domain := range domains {
   406  		if strings.HasPrefix(r.Host, bucket+"."+domain) {
   407  			u.Path = path.Join(SlashSeparator, object)
   408  			break
   409  		}
   410  	}
   411  	return u.String()
   412  }
   413  
   414  // generates ListBucketsResponse from array of BucketInfo which can be
   415  // serialized to match XML and JSON API spec output.
   416  func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
   417  	listbuckets := make([]Bucket, 0, len(buckets))
   418  	var data = ListBucketsResponse{}
   419  	var owner = Owner{
   420  		ID:          GlobalMinioDefaultOwnerID,
   421  		DisplayName: "minio",
   422  	}
   423  
   424  	for _, bucket := range buckets {
   425  		var listbucket = Bucket{}
   426  		listbucket.Name = bucket.Name
   427  		listbucket.CreationDate = bucket.Created.UTC().Format(iso8601TimeFormat)
   428  		listbuckets = append(listbuckets, listbucket)
   429  	}
   430  
   431  	data.Owner = owner
   432  	data.Buckets.Buckets = listbuckets
   433  
   434  	return data
   435  }
   436  
   437  // generates an ListBucketVersions response for the said bucket with other enumerated options.
   438  func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo) ListVersionsResponse {
   439  	versions := make([]ObjectVersion, 0, len(resp.Objects))
   440  	var owner = Owner{
   441  		ID:          GlobalMinioDefaultOwnerID,
   442  		DisplayName: "minio",
   443  	}
   444  	var data = ListVersionsResponse{}
   445  
   446  	for _, object := range resp.Objects {
   447  		var content = ObjectVersion{}
   448  		if object.Name == "" {
   449  			continue
   450  		}
   451  		content.Key = s3EncodeName(object.Name, encodingType)
   452  		content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
   453  		if object.ETag != "" {
   454  			content.ETag = "\"" + object.ETag + "\""
   455  		}
   456  		content.Size = object.Size
   457  		if object.StorageClass != "" {
   458  			content.StorageClass = object.StorageClass
   459  		} else {
   460  			content.StorageClass = globalMinioDefaultStorageClass
   461  		}
   462  		content.Owner = owner
   463  		content.VersionID = object.VersionID
   464  		if content.VersionID == "" {
   465  			content.VersionID = nullVersionID
   466  		}
   467  		content.IsLatest = object.IsLatest
   468  		content.isDeleteMarker = object.DeleteMarker
   469  		versions = append(versions, content)
   470  	}
   471  
   472  	data.Name = bucket
   473  	data.Versions = versions
   474  	data.EncodingType = encodingType
   475  	data.Prefix = s3EncodeName(prefix, encodingType)
   476  	data.KeyMarker = s3EncodeName(marker, encodingType)
   477  	data.Delimiter = s3EncodeName(delimiter, encodingType)
   478  	data.MaxKeys = maxKeys
   479  
   480  	data.NextKeyMarker = s3EncodeName(resp.NextMarker, encodingType)
   481  	data.NextVersionIDMarker = resp.NextVersionIDMarker
   482  	data.VersionIDMarker = versionIDMarker
   483  	data.IsTruncated = resp.IsTruncated
   484  
   485  	prefixes := make([]CommonPrefix, 0, len(resp.Prefixes))
   486  	for _, prefix := range resp.Prefixes {
   487  		var prefixItem = CommonPrefix{}
   488  		prefixItem.Prefix = s3EncodeName(prefix, encodingType)
   489  		prefixes = append(prefixes, prefixItem)
   490  	}
   491  	data.CommonPrefixes = prefixes
   492  	return data
   493  }
   494  
   495  // generates an ListObjectsV1 response for the said bucket with other enumerated options.
   496  func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
   497  	contents := make([]Object, 0, len(resp.Objects))
   498  	var owner = Owner{
   499  		ID:          GlobalMinioDefaultOwnerID,
   500  		DisplayName: "minio",
   501  	}
   502  	var data = ListObjectsResponse{}
   503  
   504  	for _, object := range resp.Objects {
   505  		var content = Object{}
   506  		if object.Name == "" {
   507  			continue
   508  		}
   509  		content.Key = s3EncodeName(object.Name, encodingType)
   510  		content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
   511  		if object.ETag != "" {
   512  			content.ETag = "\"" + object.ETag + "\""
   513  		}
   514  		content.Size = object.Size
   515  		if object.StorageClass != "" {
   516  			content.StorageClass = object.StorageClass
   517  		} else {
   518  			content.StorageClass = globalMinioDefaultStorageClass
   519  		}
   520  		content.Owner = owner
   521  		contents = append(contents, content)
   522  	}
   523  	data.Name = bucket
   524  	data.Contents = contents
   525  
   526  	data.EncodingType = encodingType
   527  	data.Prefix = s3EncodeName(prefix, encodingType)
   528  	data.Marker = s3EncodeName(marker, encodingType)
   529  	data.Delimiter = s3EncodeName(delimiter, encodingType)
   530  	data.MaxKeys = maxKeys
   531  	data.NextMarker = s3EncodeName(resp.NextMarker, encodingType)
   532  	data.IsTruncated = resp.IsTruncated
   533  
   534  	prefixes := make([]CommonPrefix, 0, len(resp.Prefixes))
   535  	for _, prefix := range resp.Prefixes {
   536  		var prefixItem = CommonPrefix{}
   537  		prefixItem.Prefix = s3EncodeName(prefix, encodingType)
   538  		prefixes = append(prefixes, prefixItem)
   539  	}
   540  	data.CommonPrefixes = prefixes
   541  	return data
   542  }
   543  
   544  // generates an ListObjectsV2 response for the said bucket with other enumerated options.
   545  func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter, delimiter, encodingType string, fetchOwner, isTruncated bool, maxKeys int, objects []ObjectInfo, prefixes []string, metadata bool) ListObjectsV2Response {
   546  	contents := make([]Object, 0, len(objects))
   547  	var owner = Owner{
   548  		ID:          GlobalMinioDefaultOwnerID,
   549  		DisplayName: "minio",
   550  	}
   551  	var data = ListObjectsV2Response{}
   552  
   553  	for _, object := range objects {
   554  		var content = Object{}
   555  		if object.Name == "" {
   556  			continue
   557  		}
   558  		content.Key = s3EncodeName(object.Name, encodingType)
   559  		content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
   560  		if object.ETag != "" {
   561  			content.ETag = "\"" + object.ETag + "\""
   562  		}
   563  		content.Size = object.Size
   564  		if object.StorageClass != "" {
   565  			content.StorageClass = object.StorageClass
   566  		} else {
   567  			content.StorageClass = globalMinioDefaultStorageClass
   568  		}
   569  		content.Owner = owner
   570  		if metadata {
   571  			content.UserMetadata = make(StringMap)
   572  			for k, v := range CleanMinioInternalMetadataKeys(object.UserDefined) {
   573  				if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
   574  					// Do not need to send any internal metadata
   575  					// values to client.
   576  					continue
   577  				}
   578  				// https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
   579  				if equals(k, xhttp.AmzMetaUnencryptedContentLength, xhttp.AmzMetaUnencryptedContentMD5) {
   580  					continue
   581  				}
   582  				content.UserMetadata[k] = v
   583  			}
   584  		}
   585  		contents = append(contents, content)
   586  	}
   587  	data.Name = bucket
   588  	data.Contents = contents
   589  
   590  	data.EncodingType = encodingType
   591  	data.StartAfter = s3EncodeName(startAfter, encodingType)
   592  	data.Delimiter = s3EncodeName(delimiter, encodingType)
   593  	data.Prefix = s3EncodeName(prefix, encodingType)
   594  	data.MaxKeys = maxKeys
   595  	data.ContinuationToken = base64.StdEncoding.EncodeToString([]byte(token))
   596  	data.NextContinuationToken = base64.StdEncoding.EncodeToString([]byte(nextToken))
   597  	data.IsTruncated = isTruncated
   598  
   599  	commonPrefixes := make([]CommonPrefix, 0, len(prefixes))
   600  	for _, prefix := range prefixes {
   601  		var prefixItem = CommonPrefix{}
   602  		prefixItem.Prefix = s3EncodeName(prefix, encodingType)
   603  		commonPrefixes = append(commonPrefixes, prefixItem)
   604  	}
   605  	data.CommonPrefixes = commonPrefixes
   606  	data.KeyCount = len(data.Contents) + len(data.CommonPrefixes)
   607  	return data
   608  }
   609  
   610  // generates CopyObjectResponse from etag and lastModified time.
   611  func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectResponse {
   612  	return CopyObjectResponse{
   613  		ETag:         "\"" + etag + "\"",
   614  		LastModified: lastModified.UTC().Format(iso8601TimeFormat),
   615  	}
   616  }
   617  
   618  // generates CopyObjectPartResponse from etag and lastModified time.
   619  func generateCopyObjectPartResponse(etag string, lastModified time.Time) CopyObjectPartResponse {
   620  	return CopyObjectPartResponse{
   621  		ETag:         "\"" + etag + "\"",
   622  		LastModified: lastModified.UTC().Format(iso8601TimeFormat),
   623  	}
   624  }
   625  
   626  // generates InitiateMultipartUploadResponse for given bucket, key and uploadID.
   627  func generateInitiateMultipartUploadResponse(bucket, key, uploadID string) InitiateMultipartUploadResponse {
   628  	return InitiateMultipartUploadResponse{
   629  		Bucket:   bucket,
   630  		Key:      key,
   631  		UploadID: uploadID,
   632  	}
   633  }
   634  
   635  // generates CompleteMultipartUploadResponse for given bucket, key, location and ETag.
   636  func generateCompleteMultpartUploadResponse(bucket, key, location, etag string) CompleteMultipartUploadResponse {
   637  	return CompleteMultipartUploadResponse{
   638  		Location: location,
   639  		Bucket:   bucket,
   640  		Key:      key,
   641  		// AWS S3 quotes the ETag in XML, make sure we are compatible here.
   642  		ETag: "\"" + etag + "\"",
   643  	}
   644  }
   645  
   646  // generates ListPartsResponse from ListPartsInfo.
   647  func generateListPartsResponse(partsInfo ListPartsInfo, encodingType string) ListPartsResponse {
   648  	listPartsResponse := ListPartsResponse{}
   649  	listPartsResponse.Bucket = partsInfo.Bucket
   650  	listPartsResponse.Key = s3EncodeName(partsInfo.Object, encodingType)
   651  	listPartsResponse.UploadID = partsInfo.UploadID
   652  	listPartsResponse.StorageClass = globalMinioDefaultStorageClass
   653  
   654  	// Dumb values not meaningful
   655  	listPartsResponse.Initiator = Initiator{
   656  		ID:          GlobalMinioDefaultOwnerID,
   657  		DisplayName: GlobalMinioDefaultOwnerID,
   658  	}
   659  	listPartsResponse.Owner = Owner{
   660  		ID:          GlobalMinioDefaultOwnerID,
   661  		DisplayName: GlobalMinioDefaultOwnerID,
   662  	}
   663  
   664  	listPartsResponse.MaxParts = partsInfo.MaxParts
   665  	listPartsResponse.PartNumberMarker = partsInfo.PartNumberMarker
   666  	listPartsResponse.IsTruncated = partsInfo.IsTruncated
   667  	listPartsResponse.NextPartNumberMarker = partsInfo.NextPartNumberMarker
   668  
   669  	listPartsResponse.Parts = make([]Part, len(partsInfo.Parts))
   670  	for index, part := range partsInfo.Parts {
   671  		newPart := Part{}
   672  		newPart.PartNumber = part.PartNumber
   673  		newPart.ETag = "\"" + part.ETag + "\""
   674  		newPart.Size = part.Size
   675  		newPart.LastModified = part.LastModified.UTC().Format(iso8601TimeFormat)
   676  		listPartsResponse.Parts[index] = newPart
   677  	}
   678  	return listPartsResponse
   679  }
   680  
   681  // generates ListMultipartUploadsResponse for given bucket and ListMultipartsInfo.
   682  func generateListMultipartUploadsResponse(bucket string, multipartsInfo ListMultipartsInfo, encodingType string) ListMultipartUploadsResponse {
   683  	listMultipartUploadsResponse := ListMultipartUploadsResponse{}
   684  	listMultipartUploadsResponse.Bucket = bucket
   685  	listMultipartUploadsResponse.Delimiter = s3EncodeName(multipartsInfo.Delimiter, encodingType)
   686  	listMultipartUploadsResponse.IsTruncated = multipartsInfo.IsTruncated
   687  	listMultipartUploadsResponse.EncodingType = encodingType
   688  	listMultipartUploadsResponse.Prefix = s3EncodeName(multipartsInfo.Prefix, encodingType)
   689  	listMultipartUploadsResponse.KeyMarker = s3EncodeName(multipartsInfo.KeyMarker, encodingType)
   690  	listMultipartUploadsResponse.NextKeyMarker = s3EncodeName(multipartsInfo.NextKeyMarker, encodingType)
   691  	listMultipartUploadsResponse.MaxUploads = multipartsInfo.MaxUploads
   692  	listMultipartUploadsResponse.NextUploadIDMarker = multipartsInfo.NextUploadIDMarker
   693  	listMultipartUploadsResponse.UploadIDMarker = multipartsInfo.UploadIDMarker
   694  	listMultipartUploadsResponse.CommonPrefixes = make([]CommonPrefix, len(multipartsInfo.CommonPrefixes))
   695  	for index, commonPrefix := range multipartsInfo.CommonPrefixes {
   696  		listMultipartUploadsResponse.CommonPrefixes[index] = CommonPrefix{
   697  			Prefix: s3EncodeName(commonPrefix, encodingType),
   698  		}
   699  	}
   700  	listMultipartUploadsResponse.Uploads = make([]Upload, len(multipartsInfo.Uploads))
   701  	for index, upload := range multipartsInfo.Uploads {
   702  		newUpload := Upload{}
   703  		newUpload.UploadID = upload.UploadID
   704  		newUpload.Key = s3EncodeName(upload.Object, encodingType)
   705  		newUpload.Initiated = upload.Initiated.UTC().Format(iso8601TimeFormat)
   706  		listMultipartUploadsResponse.Uploads[index] = newUpload
   707  	}
   708  	return listMultipartUploadsResponse
   709  }
   710  
   711  // generate multi objects delete response.
   712  func generateMultiDeleteResponse(quiet bool, deletedObjects []DeletedObject, errs []DeleteError) DeleteObjectsResponse {
   713  	deleteResp := DeleteObjectsResponse{}
   714  	if !quiet {
   715  		deleteResp.DeletedObjects = deletedObjects
   716  	}
   717  	if len(errs) == len(deletedObjects) {
   718  		deleteResp.DeletedObjects = nil
   719  	}
   720  	deleteResp.Errors = errs
   721  	return deleteResp
   722  }
   723  
   724  func writeResponse(w http.ResponseWriter, statusCode int, response []byte, mType mimeType) {
   725  	setCommonHeaders(w)
   726  	if mType != mimeNone {
   727  		w.Header().Set(xhttp.ContentType, string(mType))
   728  	}
   729  	w.Header().Set(xhttp.ContentLength, strconv.Itoa(len(response)))
   730  	w.WriteHeader(statusCode)
   731  	if response != nil {
   732  		w.Write(response)
   733  		w.(http.Flusher).Flush()
   734  	}
   735  }
   736  
   737  // mimeType represents various MIME type used API responses.
   738  type mimeType string
   739  
   740  const (
   741  	// Means no response type.
   742  	mimeNone mimeType = ""
   743  	// Means response type is JSON.
   744  	mimeJSON mimeType = "application/json"
   745  	// Means response type is XML.
   746  	mimeXML mimeType = "application/xml"
   747  )
   748  
   749  // writeSuccessResponseJSON writes success headers and response if any,
   750  // with content-type set to `application/json`.
   751  func writeSuccessResponseJSON(w http.ResponseWriter, response []byte) {
   752  	writeResponse(w, http.StatusOK, response, mimeJSON)
   753  }
   754  
   755  // WriteSuccessResponseXML writes success headers and response if any,
   756  // with content-type set to `application/xml`.
   757  func WriteSuccessResponseXML(w http.ResponseWriter, response []byte) {
   758  	writeResponse(w, http.StatusOK, response, mimeXML)
   759  }
   760  
   761  // writeSuccessNoContent writes success headers with http status 204
   762  func writeSuccessNoContent(w http.ResponseWriter) {
   763  	writeResponse(w, http.StatusNoContent, nil, mimeNone)
   764  }
   765  
   766  // writeRedirectSeeOther writes Location header with http status 303
   767  func writeRedirectSeeOther(w http.ResponseWriter, location string) {
   768  	w.Header().Set(xhttp.Location, location)
   769  	writeResponse(w, http.StatusSeeOther, nil, mimeNone)
   770  }
   771  
   772  func writeSuccessResponseHeadersOnly(w http.ResponseWriter) {
   773  	writeResponse(w, http.StatusOK, nil, mimeNone)
   774  }
   775  
   776  // writeErrorRespone writes error headers
   777  func WriteErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL, browser bool) {
   778  	switch err.Code {
   779  	case "SlowDown", "XMinioServerNotInitialized", "XMinioReadQuorum", "XMinioWriteQuorum":
   780  		// Set retry-after header to indicate user-agents to retry request after 120secs.
   781  		// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
   782  		w.Header().Set(xhttp.RetryAfter, "120")
   783  	case "InvalidRegion":
   784  		err.Description = fmt.Sprintf("Region does not match; expecting '%s'.", globalServerRegion)
   785  	case "AuthorizationHeaderMalformed":
   786  		err.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalServerRegion)
   787  	case "AccessDenied":
   788  		// The request is from browser and also if browser
   789  		// is enabled we need to redirect.
   790  		if browser && globalBrowserEnabled {
   791  			w.Header().Set(xhttp.Location, minioReservedBucketPath+reqURL.Path)
   792  			w.WriteHeader(http.StatusTemporaryRedirect)
   793  			return
   794  		}
   795  	}
   796  
   797  	// Generate error response.
   798  	errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path,
   799  		w.Header().Get(xhttp.AmzRequestID), globalDeploymentID)
   800  	encodedErrorResponse := EncodeResponse(errorResponse)
   801  	writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML)
   802  }
   803  
   804  func writeErrorResponseHeadersOnly(w http.ResponseWriter, err APIError) {
   805  	writeResponse(w, err.HTTPStatusCode, nil, mimeNone)
   806  }
   807  
   808  func writeErrorResponseString(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
   809  	// Generate string error response.
   810  	writeResponse(w, err.HTTPStatusCode, []byte(err.Description), mimeNone)
   811  }
   812  
   813  // writeErrorResponseJSON - writes error response in JSON format;
   814  // useful for admin APIs.
   815  func writeErrorResponseJSON(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
   816  	// Generate error response.
   817  	errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path, w.Header().Get(xhttp.AmzRequestID), globalDeploymentID)
   818  	encodedErrorResponse := encodeResponseJSON(errorResponse)
   819  	writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
   820  }
   821  
   822  // writeCustomErrorResponseJSON - similar to writeErrorResponseJSON,
   823  // but accepts the error message directly (this allows messages to be
   824  // dynamically generated.)
   825  func writeCustomErrorResponseJSON(ctx context.Context, w http.ResponseWriter, err APIError,
   826  	errBody string, reqURL *url.URL) {
   827  
   828  	reqInfo := logger.GetReqInfo(ctx)
   829  	errorResponse := APIErrorResponse{
   830  		Code:       err.Code,
   831  		Message:    errBody,
   832  		Resource:   reqURL.Path,
   833  		BucketName: reqInfo.BucketName,
   834  		Key:        reqInfo.ObjectName,
   835  		RequestID:  w.Header().Get(xhttp.AmzRequestID),
   836  		HostID:     globalDeploymentID,
   837  	}
   838  	encodedErrorResponse := encodeResponseJSON(errorResponse)
   839  	writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
   840  }