Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<String> 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<String> hostname = ConfigProvider.getConfig()
.getOptionalValue("floci.hostname", String.class);
String effectiveUrl = hostname
.map(h -> baseUrl.replaceFirst("://[^:/]+(:\\d+)?", "://" + h + "$1"))
.orElse(baseUrl);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -147,11 +155,15 @@ private static boolean isIpv4Address(String hostname) {
return true;
}

/** Returns true for *.s3.amazonaws.com and *.s3.<region>.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.<region>.amazonaws.com
if (remainder.startsWith("s3.") && remainder.endsWith(".amazonaws.com")) return true;
if (remainder.startsWith("s3.") && remainder.endsWith(".amazonaws.com")) {
return true;
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,7 +10,7 @@

class S3VirtualHostFilterTest {

// ── Virtual-hosted style: bucket prefix + matching baseHostname ─────────
// --- extractBucket with baseHostname ---

@ParameterizedTest
@CsvSource({
Expand All @@ -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({
Expand Down Expand Up @@ -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({
Expand All @@ -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));
}
}