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 }