diff --git a/api/errors/errors.go b/api/errors/errors.go index 77c3f2361..f82220e04 100644 --- a/api/errors/errors.go +++ b/api/errors/errors.go @@ -322,7 +322,7 @@ var errorCodes = errorCodeMap{ ErrInvalidMaxUploads: { ErrCode: ErrInvalidMaxUploads, Code: "InvalidArgument", - Description: "Argument max-uploads must be an integer between 0 and 2147483647", + Description: "Argument max-uploads must be an integer from 1 to 1000", HTTPStatusCode: http.StatusBadRequest, }, ErrInvalidMaxKeys: { diff --git a/api/handler/multipart_upload.go b/api/handler/multipart_upload.go index fb0c5489e..5e2ac398d 100644 --- a/api/handler/multipart_upload.go +++ b/api/handler/multipart_upload.go @@ -93,6 +93,13 @@ type ( const ( uploadIDHeaderName = "uploadId" partNumberHeaderName = "partNumber" + + prefixQueryName = "prefix" + delimiterQueryName = "delimiter" + maxUploadsQueryName = "max-uploads" + encodingTypeQueryName = "encoding-type" + keyMarkerQueryName = "key-marker" + uploadIDMarkerQueryName = "upload-id-marker" ) func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Request) { @@ -528,31 +535,28 @@ func (h *handler) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Req } var ( - queryValues = reqInfo.URL.Query() - delimiter = queryValues.Get("delimiter") - prefix = queryValues.Get("prefix") - maxUploads = layer.MaxSizeUploadsList + queryValues = reqInfo.URL.Query() + maxUploadsStr = queryValues.Get(maxUploadsQueryName) + maxUploads = layer.MaxSizeUploadsList ) - if queryValues.Get("max-uploads") != "" { - val, err := strconv.Atoi(queryValues.Get("max-uploads")) - if err != nil || val < 0 { + if maxUploadsStr != "" { + val, err := strconv.Atoi(maxUploadsStr) + if err != nil || val < 1 || val > 1000 { h.logAndSendError(w, "invalid maxUploads", reqInfo, errors.GetAPIError(errors.ErrInvalidMaxUploads)) return } - if val < maxUploads { - maxUploads = val - } + maxUploads = val } p := &layer.ListMultipartUploadsParams{ Bkt: bktInfo, - Delimiter: delimiter, - EncodingType: queryValues.Get("encoding-type"), - KeyMarker: queryValues.Get("key-marker"), + Delimiter: queryValues.Get(delimiterQueryName), + EncodingType: queryValues.Get(encodingTypeQueryName), + KeyMarker: queryValues.Get(keyMarkerQueryName), MaxUploads: maxUploads, - Prefix: prefix, - UploadIDMarker: queryValues.Get("upload-id-marker"), + Prefix: queryValues.Get(prefixQueryName), + UploadIDMarker: queryValues.Get(uploadIDMarkerQueryName), } list, err := h.obj.ListMultipartUploads(r.Context(), p) diff --git a/api/handler/multipart_upload_test.go b/api/handler/multipart_upload_test.go index f8e0a2af8..98981c590 100644 --- a/api/handler/multipart_upload_test.go +++ b/api/handler/multipart_upload_test.go @@ -5,6 +5,7 @@ import ( "encoding/xml" "net/http" "net/url" + "strconv" "testing" "time" @@ -105,6 +106,95 @@ func TestMultipartReUploadPart(t *testing.T) { equalDataSlices(t, append(data1, data2...), data) } +func TestListMultipartUploads(t *testing.T) { + hc := prepareHandlerContext(t) + + bktName := "bucket-to-list-uploads" + createTestBucket(hc, bktName) + + objName1 := "/my/object/name" + uploadInfo1 := createMultipartUpload(hc, bktName, objName1, map[string]string{}) + objName2 := "/my/object2" + uploadInfo2 := createMultipartUpload(hc, bktName, objName2, map[string]string{}) + objName3 := "/zzz/object/name3" + uploadInfo3 := createMultipartUpload(hc, bktName, objName3, map[string]string{}) + + t.Run("check upload key", func(t *testing.T) { + listUploads := listAllMultipartUploads(hc, bktName) + require.Len(t, listUploads.Uploads, 3) + for i, upload := range []*InitiateMultipartUploadResponse{uploadInfo1, uploadInfo2, uploadInfo3} { + require.Equal(t, upload.UploadID, listUploads.Uploads[i].UploadID) + require.Equal(t, upload.Key, listUploads.Uploads[i].Key) + } + }) + + t.Run("check max uploads", func(t *testing.T) { + listUploads := listMultipartUploadsBase(hc, bktName, "", "", "", "", 2) + require.Len(t, listUploads.Uploads, 2) + require.Equal(t, uploadInfo1.UploadID, listUploads.Uploads[0].UploadID) + require.Equal(t, uploadInfo2.UploadID, listUploads.Uploads[1].UploadID) + }) + + t.Run("check prefix", func(t *testing.T) { + listUploads := listMultipartUploadsBase(hc, bktName, "/my", "", "", "", -1) + require.Len(t, listUploads.Uploads, 2) + require.Equal(t, uploadInfo1.UploadID, listUploads.Uploads[0].UploadID) + require.Equal(t, uploadInfo2.UploadID, listUploads.Uploads[1].UploadID) + }) + + t.Run("check markers", func(t *testing.T) { + t.Run("check only key-marker", func(t *testing.T) { + listUploads := listMultipartUploadsBase(hc, bktName, "", "", "", objName2, -1) + require.Len(t, listUploads.Uploads, 1) + // If upload-id-marker is not specified, only the keys lexicographically greater than the specified key-marker will be included in the list. + require.Equal(t, uploadInfo3.UploadID, listUploads.Uploads[0].UploadID) + }) + + t.Run("check only upload-id-marker", func(t *testing.T) { + uploadIDMarker := uploadInfo1.UploadID + if uploadIDMarker > uploadInfo2.UploadID { + uploadIDMarker = uploadInfo2.UploadID + } + listUploads := listMultipartUploadsBase(hc, bktName, "", "", uploadIDMarker, "", -1) + // If key-marker is not specified, the upload-id-marker parameter is ignored. + require.Len(t, listUploads.Uploads, 3) + }) + + t.Run("check key-marker along with upload-id-marker", func(t *testing.T) { + uploadIDMarker := "00000000-0000-0000-0000-000000000000" + + listUploads := listMultipartUploadsBase(hc, bktName, "", "", uploadIDMarker, objName3, -1) + require.Len(t, listUploads.Uploads, 1) + // If upload-id-marker is specified, any multipart uploads for a key equal to the key-marker might also be included, + // provided those multipart uploads have upload IDs lexicographically greater than the specified upload-id-marker. + require.Equal(t, uploadInfo3.UploadID, listUploads.Uploads[0].UploadID) + }) + }) +} + +func listAllMultipartUploads(hc *handlerContext, bktName string) *ListMultipartUploadsResponse { + return listMultipartUploadsBase(hc, bktName, "", "", "", "", -1) +} + +func listMultipartUploadsBase(hc *handlerContext, bktName, prefix, delimiter, uploadIDMarker, keyMarker string, maxUploads int) *ListMultipartUploadsResponse { + query := make(url.Values) + query.Set(prefixQueryName, prefix) + query.Set(delimiterQueryName, delimiter) + query.Set(uploadIDMarkerQueryName, uploadIDMarker) + query.Set(keyMarkerQueryName, keyMarker) + if maxUploads != -1 { + query.Set(maxUploadsQueryName, strconv.Itoa(maxUploads)) + } + + w, r := prepareTestRequestWithQuery(hc, bktName, "", query, nil) + + hc.Handler().ListMultipartUploadsHandler(w, r) + listPartsResponse := &ListMultipartUploadsResponse{} + readResponse(hc.t, w, http.StatusOK, listPartsResponse) + + return listPartsResponse +} + func listParts(hc *handlerContext, bktName, objName string, uploadID string) *ListPartsResponse { return listPartsBase(hc, bktName, objName, false, uploadID) }