diff --git a/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java b/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java index cd4fbed8..81e4ee69 100644 --- a/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java +++ b/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java @@ -1,12 +1,11 @@ package io.github.hectorvent.floci.services.s3; -import jakarta.inject.Inject; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.PreMatching; import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.ext.Provider; -import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.config.ConfigProvider; import java.net.URI; import java.util.Optional; @@ -17,10 +16,15 @@ public class S3VirtualHostFilter implements ContainerRequestFilter { private final String baseHostname; - @Inject - public S3VirtualHostFilter( - @ConfigProperty(name = "floci.base-url", defaultValue = "http://localhost:4566") String baseUrl, - @ConfigProperty(name = "floci.hostname") Optional hostname) { + // Cannot use @Inject with EmulatorConfig here: @PreMatching JAX-RS filters are + // instantiated before CDI beans are fully initialized, causing SRCFG00027 at startup. + // Use programmatic ConfigProvider lookup instead. + public S3VirtualHostFilter() { + String baseUrl = ConfigProvider.getConfig() + .getOptionalValue("floci.base-url", String.class) + .orElse("http://localhost:4566"); + Optional hostname = ConfigProvider.getConfig() + .getOptionalValue("floci.hostname", String.class); String effectiveUrl = hostname .map(h -> baseUrl.replaceFirst("://[^:/]+(:\\d+)?", "://" + h + "$1")) .orElse(baseUrl); @@ -71,19 +75,21 @@ public void filter(ContainerRequestContext requestContext) { * matches a well-known AWS S3 domain pattern (for DNS-redirect setups). * * Examples with baseHostname="localhost": - * my-bucket.localhost:4566 → "my-bucket" - * my-bucket.localhost → "my-bucket" - * floci.svc.cluster.local → null (no bucket prefix, path-style) - * my-svc.floci.svc.cluster.local → null (remainder doesn't match "localhost") + * my-bucket.localhost:4566 -> "my-bucket" + * my-bucket.localhost -> "my-bucket" + * floci.svc.cluster.local -> null (no bucket prefix, path-style) + * my-svc.floci.svc.cluster.local -> null (remainder doesn't match "localhost") * * Examples with baseHostname="floci.svc.cluster.local": - * my-bucket.floci.svc.cluster.local → "my-bucket" - * floci.svc.cluster.local → null (no bucket prefix, path-style) + * my-bucket.floci.svc.cluster.local -> "my-bucket" + * floci.svc.cluster.local -> null (no bucket prefix, path-style) * * Returns null if the host does not match a virtual-hosted pattern. */ static String extractBucket(String host, String baseHostname) { - if (host == null) return null; + if (host == null) { + return null; + } // Strip port if present String hostname = stripPort(host); @@ -117,7 +123,9 @@ static String extractBucket(String host, String baseHostname) { /** Extracts the hostname (without scheme or port) from a URL string. */ static String extractHostnameFromUrl(String url) { - if (url == null) return null; + if (url == null) { + return null; + } try { URI uri = URI.create(url); return uri.getHost(); @@ -147,11 +155,15 @@ private static boolean isIpv4Address(String hostname) { return true; } - /** Returns true for *.s3.amazonaws.com and *.s3..amazonaws.com domains. */ + /** Returns true for *.s3.amazonaws.com and *.s3.region.amazonaws.com domains. */ private static boolean isAwsS3Domain(String remainder) { - if ("s3.amazonaws.com".equals(remainder)) return true; + if ("s3.amazonaws.com".equals(remainder)) { + return true; + } // s3..amazonaws.com - if (remainder.startsWith("s3.") && remainder.endsWith(".amazonaws.com")) return true; + if (remainder.startsWith("s3.") && remainder.endsWith(".amazonaws.com")) { + return true; + } return false; } } diff --git a/src/test/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilterTest.java b/src/test/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilterTest.java index 3bd18599..b84616bc 100644 --- a/src/test/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilterTest.java +++ b/src/test/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilterTest.java @@ -1,5 +1,6 @@ package io.github.hectorvent.floci.services.s3; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.NullSource; @@ -9,7 +10,7 @@ class S3VirtualHostFilterTest { - // ── Virtual-hosted style: bucket prefix + matching baseHostname ───────── + // --- extractBucket with baseHostname --- @ParameterizedTest @CsvSource({ @@ -33,7 +34,7 @@ void extractsBucketFromVirtualHostedStyle(String host, String baseHostname, Stri assertEquals(expectedBucket, S3VirtualHostFilter.extractBucket(host, baseHostname)); } - // ── Path-style: service hostname alone — must NOT extract a bucket ─────── + // --- Path-style: service hostname alone — must NOT extract a bucket --- @ParameterizedTest @CsvSource({ @@ -70,7 +71,14 @@ void returnsNullForNullHost(String host) { assertNull(S3VirtualHostFilter.extractBucket(host, "localhost")); } - // ── Hostname extraction from URL ───────────────────────────────────────── + @Test + void returnsNullForNullBaseHostname() { + // Without a baseHostname, only AWS S3 domains should match + assertNull(S3VirtualHostFilter.extractBucket("my-bucket.localhost:4566", null)); + assertEquals("my-bucket", S3VirtualHostFilter.extractBucket("my-bucket.s3.amazonaws.com", null)); + } + + // --- Hostname extraction from URL --- @ParameterizedTest @CsvSource({ @@ -83,4 +91,9 @@ void returnsNullForNullHost(String host) { void extractsHostnameFromUrl(String url, String expectedHostname) { assertEquals(expectedHostname, S3VirtualHostFilter.extractHostnameFromUrl(url)); } + + @Test + void extractHostnameFromUrlReturnsNullForNull() { + assertNull(S3VirtualHostFilter.extractHostnameFromUrl(null)); + } }