From 7ea026ff1d450fcfdfcbaa3635acf36f17dfc880 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Mon, 25 May 2020 16:51:32 -0700 Subject: [PATCH] fix: reply back user-metadata in lower case form (#9697) some clients such as veeam expect the x-amz-meta to be sent in lower cased form, while this does indeed defeats the HTTP protocol contract it is harder to change these applications, while these applications get fixed appropriately in future. x-amz-meta is usually sent in lowercased form by AWS S3 and some applications like veeam incorrectly end up relying on the case sensitivity of the HTTP headers. Bonus fixes - Fix the iso8601 time format to keep it same as AWS S3 response - Increase maxObjectList to 50,000 and use maxDeleteList as 10,000 whenever multi-object deletes are needed. --- cmd/admin-heal-ops.go | 3 +-- cmd/api-headers.go | 21 +++++++++++++----- cmd/api-response.go | 26 ++++++++++++----------- cmd/bucket-quota.go | 2 +- cmd/daily-lifecycle-ops.go | 2 +- cmd/disk-cache.go | 4 ++-- cmd/handler-utils.go | 2 ++ cmd/http/headers.go | 2 +- cmd/object-handlers.go | 7 +++--- cmd/post-policy_test.go | 21 +++++++++--------- cmd/server_test.go | 2 +- cmd/web-handlers.go | 11 +++++----- cmd/xl-zones.go | 8 +++---- mint/run/core/aws-sdk-php/quick-tests.php | 2 +- pkg/event/event.go | 2 +- 15 files changed, 64 insertions(+), 51 deletions(-) diff --git a/cmd/admin-heal-ops.go b/cmd/admin-heal-ops.go index 0c58625fd..9a10b3187 100644 --- a/cmd/admin-heal-ops.go +++ b/cmd/admin-heal-ops.go @@ -221,8 +221,7 @@ func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) ( // Check if new heal sequence to be started overlaps with any // existing, running sequence for k, hSeq := range ahs.healSeqMap { - if !hSeq.hasEnded() && (strings.HasPrefix(k, h.path) || - strings.HasPrefix(h.path, k)) { + if !hSeq.hasEnded() && (HasPrefix(k, h.path) || HasPrefix(h.path, k)) { errMsg = "The provided heal sequence path overlaps with an existing " + fmt.Sprintf("heal path: %s", k) diff --git a/cmd/api-headers.go b/cmd/api-headers.go index 518289b1a..dacd35111 100644 --- a/cmd/api-headers.go +++ b/cmd/api-headers.go @@ -83,7 +83,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp } if strings.Contains(objInfo.ETag, "-") && len(objInfo.Parts) > 0 { - w.Header().Set(xhttp.AmzMpPartsCount, strconv.Itoa(len(objInfo.Parts))) + w.Header()[xhttp.AmzMpPartsCount] = []string{strconv.Itoa(len(objInfo.Parts))} } if objInfo.ContentType != "" { @@ -106,18 +106,29 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp // Set tag count if object has tags tags, _ := url.ParseQuery(objInfo.UserTags) tagCount := len(tags) - if tagCount != 0 { - w.Header().Set(xhttp.AmzTagCount, strconv.Itoa(tagCount)) + if tagCount > 0 { + w.Header()[xhttp.AmzTagCount] = []string{strconv.Itoa(tagCount)} } // Set all other user defined metadata. for k, v := range objInfo.UserDefined { - if HasPrefix(k, ReservedMetadataPrefix) { + if strings.HasPrefix(k, ReservedMetadataPrefix) { // Do not need to send any internal metadata // values to client. continue } - w.Header().Set(k, v) + var isSet bool + for _, userMetadataPrefix := range userMetadataKeyPrefixes { + if !strings.HasPrefix(k, userMetadataPrefix) { + continue + } + w.Header()[strings.ToLower(k)] = []string{v} + isSet = true + break + } + if !isSet { + w.Header().Set(k, v) + } } totalObjectSize, err := objInfo.GetActualSize() diff --git a/cmd/api-response.go b/cmd/api-response.go index 040e90ffc..213db4d5a 100644 --- a/cmd/api-response.go +++ b/cmd/api-response.go @@ -33,8 +33,10 @@ import ( ) const ( - timeFormatAMZLong = "2006-01-02T15:04:05.000Z" // Reply date format with nanosecond precision. - maxObjectList = 10000 // Limit number of objects in a listObjectsResponse. + // RFC3339 a subset of the ISO8601 timestamp format. e.g 2014-04-29T18:30:38Z + iso8601TimeFormat = "2006-01-02T15:04:05.000Z" // Reply date format with nanosecond precision. + maxObjectList = 50000 // Limit number of objects in a listObjectsResponse/listObjectsVersionsResponse. + maxDeleteList = 10000 // Limit number of objects deleted in a delete call. maxUploadsList = 10000 // Limit number of uploads in a listUploadsResponse. maxPartsList = 10000 // Limit number of parts in a listPartsResponse. ) @@ -400,7 +402,7 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse { for _, bucket := range buckets { var listbucket = Bucket{} listbucket.Name = bucket.Name - listbucket.CreationDate = bucket.Created.UTC().Format(timeFormatAMZLong) + listbucket.CreationDate = bucket.Created.UTC().Format(iso8601TimeFormat) listbuckets = append(listbuckets, listbucket) } @@ -424,7 +426,7 @@ func generateListVersionsResponse(bucket, prefix, marker, delimiter, encodingTyp continue } content.Key = s3EncodeName(object.Name, encodingType) - content.LastModified = object.ModTime.UTC().Format(timeFormatAMZLong) + content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat) if object.ETag != "" { content.ETag = "\"" + object.ETag + "\"" } @@ -440,9 +442,9 @@ func generateListVersionsResponse(bucket, prefix, marker, delimiter, encodingTyp content.IsLatest = true versions = append(versions, content) } + data.Name = bucket data.Versions = versions - data.EncodingType = encodingType data.Prefix = s3EncodeName(prefix, encodingType) data.KeyMarker = s3EncodeName(marker, encodingType) @@ -475,7 +477,7 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingTy continue } content.Key = s3EncodeName(object.Name, encodingType) - content.LastModified = object.ModTime.UTC().Format(timeFormatAMZLong) + content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat) if object.ETag != "" { content.ETag = "\"" + object.ETag + "\"" } @@ -525,7 +527,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter, continue } content.Key = s3EncodeName(object.Name, encodingType) - content.LastModified = object.ModTime.UTC().Format(timeFormatAMZLong) + content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat) if object.ETag != "" { content.ETag = "\"" + object.ETag + "\"" } @@ -539,7 +541,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter, if metadata { content.UserMetadata = make(StringMap) for k, v := range CleanMinioInternalMetadataKeys(object.UserDefined) { - if HasPrefix(k, ReservedMetadataPrefix) { + if strings.HasPrefix(k, ReservedMetadataPrefix) { // Do not need to send any internal metadata // values to client. continue @@ -574,7 +576,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter, func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectResponse { return CopyObjectResponse{ ETag: "\"" + etag + "\"", - LastModified: lastModified.UTC().Format(timeFormatAMZLong), + LastModified: lastModified.UTC().Format(iso8601TimeFormat), } } @@ -582,7 +584,7 @@ func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectR func generateCopyObjectPartResponse(etag string, lastModified time.Time) CopyObjectPartResponse { return CopyObjectPartResponse{ ETag: "\"" + etag + "\"", - LastModified: lastModified.UTC().Format(timeFormatAMZLong), + LastModified: lastModified.UTC().Format(iso8601TimeFormat), } } @@ -627,7 +629,7 @@ func generateListPartsResponse(partsInfo ListPartsInfo, encodingType string) Lis newPart.PartNumber = part.PartNumber newPart.ETag = "\"" + part.ETag + "\"" newPart.Size = part.Size - newPart.LastModified = part.LastModified.UTC().Format(timeFormatAMZLong) + newPart.LastModified = part.LastModified.UTC().Format(iso8601TimeFormat) listPartsResponse.Parts[index] = newPart } return listPartsResponse @@ -657,7 +659,7 @@ func generateListMultipartUploadsResponse(bucket string, multipartsInfo ListMult newUpload := Upload{} newUpload.UploadID = upload.UploadID newUpload.Key = s3EncodeName(upload.Object, encodingType) - newUpload.Initiated = upload.Initiated.UTC().Format(timeFormatAMZLong) + newUpload.Initiated = upload.Initiated.UTC().Format(iso8601TimeFormat) listMultipartUploadsResponse.Uploads[index] = newUpload } return listMultipartUploadsResponse diff --git a/cmd/bucket-quota.go b/cmd/bucket-quota.go index acfac1c4e..e15e6e750 100644 --- a/cmd/bucket-quota.go +++ b/cmd/bucket-quota.go @@ -197,7 +197,7 @@ func enforceFIFOQuota(ctx context.Context, objectAPI ObjectLayer) error { numKeys := len(scorer.fileNames()) for i, key := range scorer.fileNames() { objects = append(objects, key) - if len(objects) < maxObjectList && (i < numKeys-1) { + if len(objects) < maxDeleteList && (i < numKeys-1) { // skip deletion until maxObjectList or end of slice continue } diff --git a/cmd/daily-lifecycle-ops.go b/cmd/daily-lifecycle-ops.go index 7bdfc22e7..2a21af005 100644 --- a/cmd/daily-lifecycle-ops.go +++ b/cmd/daily-lifecycle-ops.go @@ -85,7 +85,7 @@ func lifecycleRound(ctx context.Context, objAPI ObjectLayer) error { for { var objects []string for obj := range objInfoCh { - if len(objects) == maxObjectList { + if len(objects) == maxDeleteList { // Reached maximum delete requests, attempt a delete for now. break } diff --git a/cmd/disk-cache.go b/cmd/disk-cache.go index 69d7d5ce5..00c36e78e 100644 --- a/cmd/disk-cache.go +++ b/cmd/disk-cache.go @@ -99,14 +99,14 @@ func (c *cacheObjects) updateMetadataIfChanged(ctx context.Context, dcache *disk bkMeta := make(map[string]string) cacheMeta := make(map[string]string) for k, v := range bkObjectInfo.UserDefined { - if HasPrefix(k, ReservedMetadataPrefix) { + if strings.HasPrefix(k, ReservedMetadataPrefix) { // Do not need to send any internal metadata continue } bkMeta[http.CanonicalHeaderKey(k)] = v } for k, v := range cacheObjInfo.UserDefined { - if HasPrefix(k, ReservedMetadataPrefix) { + if strings.HasPrefix(k, ReservedMetadataPrefix) { // Do not need to send any internal metadata continue } diff --git a/cmd/handler-utils.go b/cmd/handler-utils.go index 660926ebb..cb2baab90 100644 --- a/cmd/handler-utils.go +++ b/cmd/handler-utils.go @@ -104,6 +104,8 @@ func isDirectiveReplace(value string) bool { var userMetadataKeyPrefixes = []string{ "X-Amz-Meta-", "X-Minio-Meta-", + "x-amz-meta-", + "x-minio-meta-", } // extractMetadata extracts metadata from HTTP header and HTTP queryString. diff --git a/cmd/http/headers.go b/cmd/http/headers.go index 261ac5911..969db491b 100644 --- a/cmd/http/headers.go +++ b/cmd/http/headers.go @@ -58,7 +58,7 @@ const ( // S3 object tagging AmzObjectTagging = "X-Amz-Tagging" - AmzTagCount = "X-Amz-Tagging-Count" + AmzTagCount = "x-amz-tagging-count" AmzTagDirective = "X-Amz-Tagging-Directive" // S3 extensions diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index 95e8d8412..fe6f090ef 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -1072,7 +1072,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re // If x-amz-tagging-directive header is REPLACE, get passed tags. if isDirectiveReplace(r.Header.Get(xhttp.AmzTagDirective)) { objTags = r.Header.Get(xhttp.AmzObjectTagging) - srcInfo.UserDefined[xhttp.AmzTagDirective] = replaceDirective if _, err := tags.ParseObjectTags(objTags); err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return @@ -1096,7 +1095,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re retentionMode, retentionDate, legalHold, s3Err := checkPutObjectLockAllowed(ctx, r, dstBucket, dstObject, getObjectInfo, retPerms, holdPerms) if s3Err == ErrNone && retentionMode.Valid() { srcInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockMode)] = string(retentionMode) - srcInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = retentionDate.UTC().Format(time.RFC3339) + srcInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = retentionDate.UTC().Format(iso8601TimeFormat) } if s3Err == ErrNone && legalHold.Status.Valid() { srcInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockLegalHold)] = string(legalHold.Status) @@ -1429,7 +1428,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req retentionMode, retentionDate, legalHold, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, getObjectInfo, retPerms, holdPerms) if s3Err == ErrNone && retentionMode.Valid() { metadata[strings.ToLower(xhttp.AmzObjectLockMode)] = string(retentionMode) - metadata[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = retentionDate.UTC().Format(time.RFC3339) + metadata[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = retentionDate.UTC().Format(iso8601TimeFormat) } if s3Err == ErrNone && legalHold.Status.Valid() { metadata[strings.ToLower(xhttp.AmzObjectLockLegalHold)] = string(legalHold.Status) @@ -1605,7 +1604,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r retentionMode, retentionDate, legalHold, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, getObjectInfo, retPerms, holdPerms) if s3Err == ErrNone && retentionMode.Valid() { metadata[strings.ToLower(xhttp.AmzObjectLockMode)] = string(retentionMode) - metadata[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = retentionDate.UTC().Format(time.RFC3339) + metadata[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = retentionDate.UTC().Format(iso8601TimeFormat) } if s3Err == ErrNone && legalHold.Status.Valid() { metadata[strings.ToLower(xhttp.AmzObjectLockLegalHold)] = string(legalHold.Status) diff --git a/cmd/post-policy_test.go b/cmd/post-policy_test.go index 2d58f01cd..005b22a0a 100644 --- a/cmd/post-policy_test.go +++ b/cmd/post-policy_test.go @@ -33,14 +33,13 @@ import ( ) const ( - expirationDateFormat = "2006-01-02T15:04:05.999Z" - iso8601DateFormat = "20060102T150405Z" + iso8601DateFormat = "20060102T150405Z" ) func newPostPolicyBytesV4WithContentRange(credential, bucketName, objectKey string, expiration time.Time) []byte { t := UTCNow() // Add the expiration date. - expirationStr := fmt.Sprintf(`"expiration": "%s"`, expiration.Format(expirationDateFormat)) + expirationStr := fmt.Sprintf(`"expiration": "%s"`, expiration.Format(iso8601TimeFormat)) // Add the bucket condition, only accept buckets equal to the one passed. bucketConditionStr := fmt.Sprintf(`["eq", "$bucket", "%s"]`, bucketName) // Add the key condition, only accept keys equal to the one passed. @@ -71,7 +70,7 @@ func newPostPolicyBytesV4WithContentRange(credential, bucketName, objectKey stri func newPostPolicyBytesV4(credential, bucketName, objectKey string, expiration time.Time) []byte { t := UTCNow() // Add the expiration date. - expirationStr := fmt.Sprintf(`"expiration": "%s"`, expiration.Format(expirationDateFormat)) + expirationStr := fmt.Sprintf(`"expiration": "%s"`, expiration.Format(iso8601TimeFormat)) // Add the bucket condition, only accept buckets equal to the one passed. bucketConditionStr := fmt.Sprintf(`["eq", "$bucket", "%s"]`, bucketName) // Add the key condition, only accept keys equal to the one passed. @@ -98,7 +97,7 @@ func newPostPolicyBytesV4(credential, bucketName, objectKey string, expiration t // newPostPolicyBytesV2 - creates a bare bones postpolicy string with key and bucket matches. func newPostPolicyBytesV2(bucketName, objectKey string, expiration time.Time) []byte { // Add the expiration date. - expirationStr := fmt.Sprintf(`"expiration": "%s"`, expiration.Format(expirationDateFormat)) + expirationStr := fmt.Sprintf(`"expiration": "%s"`, expiration.Format(iso8601TimeFormat)) // Add the bucket condition, only accept buckets equal to the one passed. bucketConditionStr := fmt.Sprintf(`["eq", "$bucket", "%s"]`, bucketName) // Add the key condition, only accept keys equal to the one passed. @@ -264,7 +263,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr expectedRespStatus: http.StatusNoContent, accessKey: credentials.AccessKey, secretKey: credentials.SecretKey, - dates: []interface{}{curTimePlus5Min.Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, + dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"]]}`, }, // Corrupted Base 64 result @@ -274,7 +273,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr expectedRespStatus: http.StatusBadRequest, accessKey: credentials.AccessKey, secretKey: credentials.SecretKey, - dates: []interface{}{curTimePlus5Min.Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, + dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"]]}`, corruptedBase64: true, }, @@ -285,7 +284,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr expectedRespStatus: http.StatusBadRequest, accessKey: credentials.AccessKey, secretKey: credentials.SecretKey, - dates: []interface{}{curTimePlus5Min.Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, + dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"]]}`, corruptedMultipart: true, }, @@ -307,7 +306,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr expectedRespStatus: http.StatusForbidden, accessKey: credentials.AccessKey, secretKey: credentials.SecretKey, - dates: []interface{}{curTime.Add(-1 * time.Minute * 5).Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, + dates: []interface{}{curTime.Add(-1 * time.Minute * 5).Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"]]}`, }, // Corrupted policy document @@ -317,7 +316,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr expectedRespStatus: http.StatusForbidden, accessKey: credentials.AccessKey, secretKey: credentials.SecretKey, - dates: []interface{}{curTimePlus5Min.Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, + dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, policy: `{"3/aws4_request"]]}`, }, } @@ -460,7 +459,7 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() - dates := []interface{}{curTimePlus5Min.Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)} + dates := []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)} policy := `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], {"success_action_redirect":"` + redirectURL.String() + `"},["starts-with", "$key", "test/"], ["eq", "$x-amz-meta-uuid", "1234"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"]]}` // Generate the final policy document diff --git a/cmd/server_test.go b/cmd/server_test.go index 4e785b049..0445f076a 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -1236,7 +1236,7 @@ func (s *TestSuiteCommon) TestListBuckets(c *check) { c.Assert(createdBucket.Name != "", true) // Parse the bucket modtime - creationTime, err := time.Parse(timeFormatAMZLong, createdBucket.CreationDate) + creationTime, err := time.Parse(iso8601TimeFormat, createdBucket.CreationDate) c.Assert(err, nil) // Check if bucket modtime is consistent (not less than current time and not late more than 5 minutes) diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index e30c1ed6d..993a964b0 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -409,8 +409,8 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r nextMarker := "" // Fetch all the objects for { - result, err := core.ListObjects(args.BucketName, args.Prefix, nextMarker, SlashSeparator, - maxObjectList) + // Let listObjects reply back the maximum from server implementation + result, err := core.ListObjects(args.BucketName, args.Prefix, nextMarker, SlashSeparator, 0) if err != nil { return toJSONError(ctx, err, args.BucketName) } @@ -524,7 +524,8 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r nextMarker := "" // Fetch all the objects for { - lo, err := listObjects(ctx, args.BucketName, args.Prefix, nextMarker, SlashSeparator, maxObjectList) + // Limit browser to 1000 batches to be more responsive, scrolling friendly. + lo, err := listObjects(ctx, args.BucketName, args.Prefix, nextMarker, SlashSeparator, 1000) if err != nil { return &json2.Error{Message: err.Error()} } @@ -761,7 +762,7 @@ next: for { var objects []string for obj := range objInfoCh { - if len(objects) == maxObjectList { + if len(objects) == maxDeleteList { // Reached maximum delete requests, attempt a delete for now. break } @@ -1122,7 +1123,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { retentionMode, retentionDate, legalHold, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, getObjectInfo, retPerms, holdPerms) if s3Err == ErrNone && retentionMode != "" { opts.UserDefined[xhttp.AmzObjectLockMode] = string(retentionMode) - opts.UserDefined[xhttp.AmzObjectLockRetainUntilDate] = retentionDate.UTC().Format(time.RFC3339) + opts.UserDefined[xhttp.AmzObjectLockRetainUntilDate] = retentionDate.UTC().Format(iso8601TimeFormat) } if s3Err == ErrNone && legalHold.Status != "" { opts.UserDefined[xhttp.AmzObjectLockLegalHold] = string(legalHold.Status) diff --git a/cmd/xl-zones.go b/cmd/xl-zones.go index 85fae9d05..db92313b7 100644 --- a/cmd/xl-zones.go +++ b/cmd/xl-zones.go @@ -1046,7 +1046,7 @@ func (z *xlZones) PutObjectPart(ctx context.Context, bucket, object, uploadID st return z.zones[0].PutObjectPart(ctx, bucket, object, uploadID, partID, data, opts) } for _, zone := range z.zones { - result, err := zone.ListMultipartUploads(ctx, bucket, object, "", "", "", maxObjectList) + result, err := zone.ListMultipartUploads(ctx, bucket, object, "", "", "", maxUploadsList) if err != nil { return PartInfo{}, err } @@ -1078,7 +1078,7 @@ func (z *xlZones) ListObjectParts(ctx context.Context, bucket, object, uploadID return z.zones[0].ListObjectParts(ctx, bucket, object, uploadID, partNumberMarker, maxParts, opts) } for _, zone := range z.zones { - result, err := zone.ListMultipartUploads(ctx, bucket, object, "", "", "", maxObjectList) + result, err := zone.ListMultipartUploads(ctx, bucket, object, "", "", "", maxUploadsList) if err != nil { return ListPartsInfo{}, err } @@ -1110,7 +1110,7 @@ func (z *xlZones) AbortMultipartUpload(ctx context.Context, bucket, object, uplo } for _, zone := range z.zones { - result, err := zone.ListMultipartUploads(ctx, bucket, object, "", "", "", maxObjectList) + result, err := zone.ListMultipartUploads(ctx, bucket, object, "", "", "", maxUploadsList) if err != nil { return err } @@ -1157,7 +1157,7 @@ func (z *xlZones) CompleteMultipartUpload(ctx context.Context, bucket, object, u } for _, zone := range z.zones { - result, err := zone.ListMultipartUploads(ctx, bucket, object, "", "", "", maxObjectList) + result, err := zone.ListMultipartUploads(ctx, bucket, object, "", "", "", maxUploadsList) if err != nil { return objInfo, err } diff --git a/mint/run/core/aws-sdk-php/quick-tests.php b/mint/run/core/aws-sdk-php/quick-tests.php index dc5d074f4..0af49fb53 100644 --- a/mint/run/core/aws-sdk-php/quick-tests.php +++ b/mint/run/core/aws-sdk-php/quick-tests.php @@ -33,7 +33,7 @@ const HTTP_NOCONTENT = "204"; const HTTP_BADREQUEST = "400"; const HTTP_NOTIMPLEMENTED = "501"; const HTTP_INTERNAL_ERROR = "500"; -const TEST_METADATA = ['Param_1' => 'val-1']; +const TEST_METADATA = ['param_1' => 'val-1']; /** * ClientConfig abstracts configuration details to connect to a diff --git a/pkg/event/event.go b/pkg/event/event.go index 570eeb11e..85fb8e9ab 100644 --- a/pkg/event/event.go +++ b/pkg/event/event.go @@ -24,7 +24,7 @@ const ( AccessFormat = "access" // AMZTimeFormat - event time format. - AMZTimeFormat = "2006-01-02T15:04:05Z" + AMZTimeFormat = "2006-01-02T15:04:05.000Z" ) // Identity represents access key who caused the event.