From adb841e516bd96d0a6ad77f7cd998eb0f7f8a237 Mon Sep 17 00:00:00 2001 From: Sandra <229708855+SandraNelj@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:14:35 +0100 Subject: [PATCH 1/4] This PR updates the CORS filter so that allowed origins and HTTP methods are now loaded from the application configuration file instead of being hardcoded. No more hardcoded CORS rules in code. Makes it easier to add or change allowed origins/methods without changing code. Improves flexibility and maintainability for future CORS changes. --- .../java/org/juv25d/filter/CorsFilter.java | 20 ++++++----- .../java/org/juv25d/util/ConfigLoader.java | 35 +++++++++++++++++++ src/main/resources/application-properties.yml | 11 ++++++ 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/juv25d/filter/CorsFilter.java b/src/main/java/org/juv25d/filter/CorsFilter.java index 141251b1..ccd8c144 100644 --- a/src/main/java/org/juv25d/filter/CorsFilter.java +++ b/src/main/java/org/juv25d/filter/CorsFilter.java @@ -2,20 +2,22 @@ import org.juv25d.http.HttpRequest; import org.juv25d.http.HttpResponse; - +import org.juv25d.util.ConfigLoader; import java.io.IOException; +import java.util.HashSet; import java.util.Map; import java.util.Set; public class CorsFilter implements Filter { - // Whitelist, allow known origins: - private static final Set ALLOWED_ORIGINS = Set.of( - "http://localhost:3000" - ); + private final Set allowedOrigins; + private final String allowedMethods; - // Supported methods - private static final String ALLOWED_METHODS = "GET, POST, PUT, PATCH, DELETE, OPTIONS"; + public CorsFilter() { + ConfigLoader config = ConfigLoader.getInstance(); + this.allowedOrigins = new HashSet<>(config.getAllowedOrigins()); + this.allowedMethods = String.join(",", config.getAllowedMethods()); + } @Override public void doFilter(HttpRequest req, HttpResponse res, FilterChain chain) throws IOException { @@ -28,7 +30,7 @@ public void doFilter(HttpRequest req, HttpResponse res, FilterChain chain) throw } // Origin exists but are not allowed, return no CORS headers - if (!ALLOWED_ORIGINS.contains(origin)) { + if (!allowedOrigins.contains(origin)) { chain.doFilter(req, res); return; } @@ -44,7 +46,7 @@ public void doFilter(HttpRequest req, HttpResponse res, FilterChain chain) throw // Preflight, OPTIONS if ("OPTIONS".equalsIgnoreCase(req.method())) { - res.setHeader("Access-Control-Allow-Methods", ALLOWED_METHODS); + res.setHeader("Access-Control-Allow-Methods", allowedMethods); // If browser requests specific headers, mirror String requestedHeaders = header(req.headers(), "Access-Control-Request-Headers"); diff --git a/src/main/java/org/juv25d/util/ConfigLoader.java b/src/main/java/org/juv25d/util/ConfigLoader.java index b89550fc..f3ce70bb 100644 --- a/src/main/java/org/juv25d/util/ConfigLoader.java +++ b/src/main/java/org/juv25d/util/ConfigLoader.java @@ -6,6 +6,7 @@ import java.io.InputStream; import java.util.*; +import java.util.stream.Collectors; public class ConfigLoader { @Nullable private static ConfigLoader instance; @@ -17,6 +18,8 @@ public class ConfigLoader { private boolean rateLimitingEnabled; private List trustedProxies; private List proxyRoutes = new ArrayList<>(); + private List allowedOrigins = List.of(); + private List allowedMethods = List.of(); private ConfigLoader() { loadConfiguration(getClass().getClassLoader() @@ -52,11 +55,14 @@ private void loadConfiguration(InputStream input) { this.rootDirectory = "static"; this.logLevel = "INFO"; this.trustedProxies = List.of(); + this.allowedOrigins = List.of(); + this.allowedMethods = List.of(); // server Object serverObj = config.get("server"); if (serverObj != null) { Map serverConfig = asStringObjectMap(serverObj); + Object portValue = serverConfig.get("port"); if (portValue instanceof Number n) this.port = n.intValue(); @@ -113,6 +119,27 @@ private void loadConfiguration(InputStream input) { Long.parseLong(String.valueOf(rateLimitingConfig.getOrDefault("burst-capacity", 100L))); } + //Cors + Object corsObj = config.get("cors"); + if (corsObj != null) { + Map corsConfig = asStringObjectMap(corsObj); + + Object origins = corsConfig.get("allowed-origins"); + if (origins instanceof List list) { + this.allowedOrigins = list.stream() + .map(String::valueOf) + .toList(); + } + + Object methods = corsConfig.get("allowed-methods"); + if (methods instanceof List methodList) { + this.allowedMethods = methodList.stream() + .map(String::valueOf) + .toList(); + } else { + this.allowedMethods = List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"); + } + } } catch (Exception e) { throw new RuntimeException("Failed to load application config", e); } @@ -157,4 +184,12 @@ public boolean isRateLimitingEnabled() { public List getProxyRoutes() { return Collections.unmodifiableList(proxyRoutes); } + + public List getAllowedOrigins() { + return allowedOrigins; + } + public List getAllowedMethods() { + return allowedMethods; + } + } diff --git a/src/main/resources/application-properties.yml b/src/main/resources/application-properties.yml index b1363129..28eb077d 100644 --- a/src/main/resources/application-properties.yml +++ b/src/main/resources/application-properties.yml @@ -15,3 +15,14 @@ rate-limiting: enabled: true requests-per-minute: 60 burst-capacity: 100 + +cors: + allowed-origins: + - http://localhost:3000 + allowed-methods: + - GET + - POST + - PUT + - PATCH + - DELETE + - OPTIONS From 2b13950d4576584773816a27eaf18e042aa8b7bb Mon Sep 17 00:00:00 2001 From: Sandra <229708855+SandraNelj@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:20:01 +0100 Subject: [PATCH 2/4] Fixed whitespace in ConfigLoader.java --- src/main/java/org/juv25d/util/ConfigLoader.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/juv25d/util/ConfigLoader.java b/src/main/java/org/juv25d/util/ConfigLoader.java index f3ce70bb..b0a546a9 100644 --- a/src/main/java/org/juv25d/util/ConfigLoader.java +++ b/src/main/java/org/juv25d/util/ConfigLoader.java @@ -3,10 +3,9 @@ import org.jspecify.annotations.Nullable; import org.juv25d.proxy.ProxyRoute; import org.yaml.snakeyaml.Yaml; - import java.io.InputStream; import java.util.*; -import java.util.stream.Collectors; + public class ConfigLoader { @Nullable private static ConfigLoader instance; From 4cc7df3d3440651baee3e4f06552764b015e0cb8 Mon Sep 17 00:00:00 2001 From: Sandra <229708855+SandraNelj@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:10:12 +0100 Subject: [PATCH 3/4] Fix CORS configuration defaults and allowed methods --- src/main/java/org/juv25d/util/ConfigLoader.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/juv25d/util/ConfigLoader.java b/src/main/java/org/juv25d/util/ConfigLoader.java index b0a546a9..d2f74e0c 100644 --- a/src/main/java/org/juv25d/util/ConfigLoader.java +++ b/src/main/java/org/juv25d/util/ConfigLoader.java @@ -55,7 +55,7 @@ private void loadConfiguration(InputStream input) { this.logLevel = "INFO"; this.trustedProxies = List.of(); this.allowedOrigins = List.of(); - this.allowedMethods = List.of(); + this.allowedMethods = List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"); // server Object serverObj = config.get("server"); @@ -132,9 +132,12 @@ private void loadConfiguration(InputStream input) { Object methods = corsConfig.get("allowed-methods"); if (methods instanceof List methodList) { - this.allowedMethods = methodList.stream() + List parsedMethods = methodList.stream() .map(String::valueOf) .toList(); + if (!parsedMethods.isEmpty()) { + this.allowedMethods = parsedMethods; + } } else { this.allowedMethods = List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"); } From 1eb37236d0869c9b4b0a30cc7f31b69d31c359a3 Mon Sep 17 00:00:00 2001 From: Sandra <229708855+SandraNelj@users.noreply.github.com> Date: Fri, 27 Feb 2026 09:12:09 +0100 Subject: [PATCH 4/4] Harden CORS configuration parsing and ensure safe defaults after feedback from CodeRabbit. Ensures safe default HTTP methods are always set, even if cors is missing in YAML. Hardens parsing of allowed-origins and allowed-methods: Filters out null and blank values Trims whitespace Normalizes HTTP methods to uppercase --- .../java/org/juv25d/util/ConfigLoader.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/juv25d/util/ConfigLoader.java b/src/main/java/org/juv25d/util/ConfigLoader.java index d2f74e0c..617f3f57 100644 --- a/src/main/java/org/juv25d/util/ConfigLoader.java +++ b/src/main/java/org/juv25d/util/ConfigLoader.java @@ -123,23 +123,31 @@ private void loadConfiguration(InputStream input) { if (corsObj != null) { Map corsConfig = asStringObjectMap(corsObj); + //Allowed-origins Object origins = corsConfig.get("allowed-origins"); if (origins instanceof List list) { this.allowedOrigins = list.stream() + .filter(Objects::nonNull) .map(String::valueOf) + .map(String::trim) + .filter(s -> !s.isEmpty()) .toList(); } + //Allowed-methods Object methods = corsConfig.get("allowed-methods"); if (methods instanceof List methodList) { List parsedMethods = methodList.stream() + .filter(Objects::nonNull) .map(String::valueOf) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .map(s -> s.toUpperCase(Locale.ROOT)) .toList(); - if (!parsedMethods.isEmpty()) { - this.allowedMethods = parsedMethods; - } - } else { - this.allowedMethods = List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"); + + this.allowedMethods = parsedMethods.isEmpty() + ? List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") + : parsedMethods; } } } catch (Exception e) {