From 5dd34e32607075f4cd22c9c5ba922a479ce1e2bb Mon Sep 17 00:00:00 2001 From: chrislu Date: Mon, 15 Dec 2025 23:35:55 -0800 Subject: [PATCH] s3: fix ListObjectVersions pagination by implementing key-marker filtering The ListObjectVersions API was receiving key-marker and version-id-marker parameters but not using them to filter results. This caused infinite pagination loops when clients tried to paginate through results. Fix by adding filtering logic after sorting: - Skip versions with key < keyMarker (already returned in previous pages) - For key == keyMarker, skip versions with versionId >= versionIdMarker - Include versions with key > keyMarker or (key == keyMarker and versionId < versionIdMarker) This respects the S3 sort order (key ascending, versionId descending for same key) and correctly returns only versions that come AFTER the marker position. --- weed/s3api/s3api_object_versioning.go | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/weed/s3api/s3api_object_versioning.go b/weed/s3api/s3api_object_versioning.go index f33d90c8b..86b1078af 100644 --- a/weed/s3api/s3api_object_versioning.go +++ b/weed/s3api/s3api_object_versioning.go @@ -226,6 +226,38 @@ func (s3a *S3ApiServer) listObjectVersions(bucket, prefix, keyMarker, versionIdM return versionIdI > versionIdJ }) + // Apply key-marker and version-id-marker filtering + // S3 pagination: skip versions at or before the marker, return versions AFTER the marker + // Versions are sorted: key ascending, then versionId descending (newest first for same key) + if keyMarker != "" { + filteredVersions := make([]interface{}, 0, len(allVersions)) + for _, version := range allVersions { + var key, versionId string + switch v := version.(type) { + case *VersionEntry: + key = v.Key + versionId = v.VersionId + case *DeleteMarkerEntry: + key = v.Key + versionId = v.VersionId + } + + // Include this version if it's AFTER the marker + // For key > keyMarker: always include + // For key == keyMarker: include if versionId < versionIdMarker (since versionIds are descending) + // For key < keyMarker: skip (already returned in previous pages) + if key > keyMarker { + filteredVersions = append(filteredVersions, version) + } else if key == keyMarker && versionIdMarker != "" && versionId < versionIdMarker { + filteredVersions = append(filteredVersions, version) + } + // else: skip this version (it was in a previous page) + } + glog.V(1).Infof("listObjectVersions: after applying markers (key=%s, versionId=%s), %d -> %d versions", + keyMarker, versionIdMarker, len(allVersions), len(filteredVersions)) + allVersions = filteredVersions + } + // Build result using S3ListObjectVersionsResult to avoid conflicts with XSD structs result := &S3ListObjectVersionsResult{ Name: bucket,