diff --git a/src/main/java/com/glencoesoftware/omero/zarr/ZarrStore.java b/src/main/java/com/glencoesoftware/omero/zarr/ZarrStore.java index 1dec9e9a..0a149c5f 100644 --- a/src/main/java/com/glencoesoftware/omero/zarr/ZarrStore.java +++ b/src/main/java/com/glencoesoftware/omero/zarr/ZarrStore.java @@ -108,12 +108,12 @@ public class ZarrStore { */ public ZarrStore(final String orgPath) throws URISyntaxException, IllegalArgumentException, IOException, ZarrException { - this.path = orgPath; - int zarrIndex = orgPath.lastIndexOf(".zarr"); + this.path = normalizePath(orgPath); + int zarrIndex = path.lastIndexOf(".zarr"); if (zarrIndex < 0) { throw new IllegalArgumentException("Path is not a .zarr"); } - String pathToZarr = orgPath.substring(0, zarrIndex + 5); + String pathToZarr = path.substring(0, zarrIndex + 5); if (!path.contains("://") || path.startsWith("file")) { int sep = path.lastIndexOf(File.separator); String storePath = path.substring(0, sep); @@ -227,6 +227,23 @@ public static String[] splitOnQuery(String uri) { return pathAndQuery; } + /** + * Removes trailing slashes from the path component of the given URI while preserving any + * query string. Trailing slashes otherwise produce empty path segments when the URI is + * split, which leads to malformed S3 keys such as {@code path//zarr.json}. + * + * @param orgPath the original URI, possibly ending in one or more slashes + * @return the URI with trailing slashes removed from the path component + */ + static String normalizePath(String orgPath) { + String[] pathAndQuery = splitOnQuery(orgPath); + String normalized = pathAndQuery[0]; + while (normalized.endsWith("/")) { + normalized = normalized.substring(0, normalized.length() - 1); + } + return normalized + (pathAndQuery[1] != null ? pathAndQuery[1] : ""); + } + private void assertNoAwsSystemEnvCredentials() { // If AWS Environment or System Properties are set, throw an exception // so users will know they are not supported diff --git a/src/test/java/com/glencoesoftware/omero/zarr/ZarrStoreTest.java b/src/test/java/com/glencoesoftware/omero/zarr/ZarrStoreTest.java index 5ea7ad97..015da54a 100644 --- a/src/test/java/com/glencoesoftware/omero/zarr/ZarrStoreTest.java +++ b/src/test/java/com/glencoesoftware/omero/zarr/ZarrStoreTest.java @@ -66,4 +66,37 @@ public void testRemoveNothing() { Assert.assertEquals("/my/test/path.zarr", pathAndQuery[0]); Assert.assertNull(pathAndQuery[1]); } + + @Test + public void testNormalizePathStripsTrailingSlashes() { + Assert.assertEquals( + "s3://host/bucket/image.zarr/0?anonymous=true", + ZarrStore.normalizePath( + "s3://host/bucket/image.zarr/0/?anonymous=true")); + + Assert.assertEquals( + "s3://host/bucket/image.zarr/0?anonymous=true", + ZarrStore.normalizePath( + "s3://host/bucket/image.zarr/0///?anonymous=true")); + + Assert.assertEquals( + "s3://host/bucket/image.zarr/0", + ZarrStore.normalizePath( + "s3://host/bucket/image.zarr/0/")); + + Assert.assertEquals( + "https://host/image.zarr", + ZarrStore.normalizePath( + "https://host/image.zarr/")); + + Assert.assertEquals( + "s3://host/bucket/image.zarr", + ZarrStore.normalizePath( + "s3://host/bucket/image.zarr")); + + Assert.assertEquals( + "/my/test/path.zarr?anonymous=true", + ZarrStore.normalizePath( + "/my/test/path.zarr?anonymous=true")); + } }