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 }