From d75b656711bc8e13d275422b876c4cb684c2f1f4 Mon Sep 17 00:00:00 2001 From: Eric Thilen Date: Thu, 26 Feb 2026 12:51:44 +0100 Subject: [PATCH 1/3] Store query parameters separately from path --- .../org/example/httpparser/HttpRequest.java | 108 ++++++++++++------ 1 file changed, 71 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/example/httpparser/HttpRequest.java b/src/main/java/org/example/httpparser/HttpRequest.java index 18f0e561..3c328107 100644 --- a/src/main/java/org/example/httpparser/HttpRequest.java +++ b/src/main/java/org/example/httpparser/HttpRequest.java @@ -2,48 +2,82 @@ import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; /* -* -*This class groups together all information about a request that the server needs + * + * This class groups together all information about a request that the server needs */ public class HttpRequest { - private final String method; - private final String path; - private final String version; - private final Map headers; - private final String body; - private final Map attributes = new HashMap<>(); - - public HttpRequest(String method, - String path, - String version, - Map headers, - String body) { - this.method = method; - this.path = path; - this.version = version; - this.headers = headers != null ? Map.copyOf(headers) : Collections.emptyMap(); - this.body = body; - } - - public String getMethod() { - return method; } - public String getPath() { - return path; } - public String getVersion() { - return version; } - public Map getHeaders() { - return headers; } - public String getBody() { - return body; } - public void setAttribute(String key, Object value) { - attributes.put(key, value); - } - public Object getAttribute(String key) { - return attributes.get(key); - } + private final String method; + private final String path; + private final String version; + private final Map headers; + private final String body; + + private final Map> queryParams; + + private final Map attributes = new HashMap<>(); + + public HttpRequest(String method, + String path, + String version, + Map headers, + String body) { + this(method, path, version, headers, body, Collections.emptyMap()); + } + + public HttpRequest(String method, + String path, + String version, + Map headers, + String body, + Map> queryParams) { + + this.method = method; + this.path = path; + this.version = version; + this.headers = headers != null ? Map.copyOf(headers) : Collections.emptyMap(); + this.body = body; + this.queryParams = queryParams != null ? Map.copyOf(queryParams) : Collections.emptyMap(); + } + + public String getMethod() { + return method; + } + + public String getPath() { + return path; + } + + public String getVersion() { + return version; + } + + public Map getHeaders() { + return headers; + } + + public String getBody() { + return body; + } + + public Map> getQueryParams() { + return queryParams; + } + + public List getQueryParam(String key) { + return queryParams.getOrDefault(key, List.of()); + } + + public void setAttribute(String key, Object value) { + attributes.put(key, value); + } + + public Object getAttribute(String key) { + return attributes.get(key); } +} \ No newline at end of file From e4a4f90545411b47f23f310323eef7c339cb5211 Mon Sep 17 00:00:00 2001 From: Eric Thilen Date: Thu, 26 Feb 2026 12:52:01 +0100 Subject: [PATCH 2/3] Extract path and parse URL-decoded query parameters --- .../java/org/example/ConnectionHandler.java | 79 ++++++++++++++++--- 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/example/ConnectionHandler.java b/src/main/java/org/example/ConnectionHandler.java index 1fcc29fb..42dbe088 100644 --- a/src/main/java/org/example/ConnectionHandler.java +++ b/src/main/java/org/example/ConnectionHandler.java @@ -4,8 +4,12 @@ import org.example.filter.IpFilter; import org.example.httpparser.HttpParser; import org.example.httpparser.HttpRequest; + import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; + import org.example.filter.Filter; import org.example.filter.FilterChainImpl; import org.example.http.HttpResponseBuilder; @@ -13,6 +17,8 @@ import java.io.IOException; import java.net.Socket; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; public class ConnectionHandler implements AutoCloseable { @@ -40,7 +46,6 @@ private List buildFilters() { if (Boolean.TRUE.equals(ipFilterConfig.enabled())) { list.add(createIpFilterFromConfig(ipFilterConfig)); } - // Add more filters here... return list; } @@ -58,12 +63,18 @@ public void runConnectionHandler() throws IOException { parser.parseRequest(); parser.parseHttp(); + // --- ISSUE FIX --- + String rawUri = parser.getUri(); + String pathOnly = extractPath(rawUri); + Map> queryParams = parseQueryParams(rawUri); + HttpRequest request = new HttpRequest( parser.getMethod(), - parser.getUri(), + pathOnly, parser.getVersion(), parser.getHeadersMap(), - "" + "", + queryParams ); String clientIp = client.getInetAddress().getHostAddress(); @@ -80,16 +91,14 @@ public void runConnectionHandler() throws IOException { return; } - resolveTargetFile(parser.getUri()); + resolveTargetFile(request.getPath()); sfh.sendGetRequest(client.getOutputStream(), uri); } private HttpResponseBuilder applyFilters(HttpRequest request) { HttpResponseBuilder response = new HttpResponseBuilder(); - FilterChainImpl chain = new FilterChainImpl(filters); chain.doFilter(request, response); - return response; } @@ -101,6 +110,61 @@ private void resolveTargetFile(String uri) { } } + // ----------------------------- + // Query parsing without split() + // ----------------------------- + + private static String extractPath(String uri) { + if (uri == null) return "/"; + int q = uri.indexOf('?'); + return (q >= 0) ? uri.substring(0, q) : uri; + } + + private static Map> parseQueryParams(String uri) { + Map> params = new HashMap<>(); + if (uri == null) return params; + + int questionMarkIndex = uri.indexOf('?'); + if (questionMarkIndex < 0 || questionMarkIndex == uri.length() - 1) { + return params; + } + + String query = uri.substring(questionMarkIndex + 1); + + int start = 0; + while (start < query.length()) { + + int ampIndex = query.indexOf('&', start); + if (ampIndex == -1) { + ampIndex = query.length(); + } + + String pair = query.substring(start, ampIndex); + + int equalsIndex = pair.indexOf('='); + + String key; + String value; + + if (equalsIndex >= 0) { + key = pair.substring(0, equalsIndex); + value = pair.substring(equalsIndex + 1); + } else { + key = pair; + value = ""; + } + + key = URLDecoder.decode(key, StandardCharsets.UTF_8); + value = URLDecoder.decode(value, StandardCharsets.UTF_8); + + params.computeIfAbsent(key, k -> new ArrayList<>()).add(value); + + start = ampIndex + 1; + } + + return params; + } + @Override public void close() throws Exception { client.close(); @@ -109,19 +173,16 @@ public void close() throws Exception { private IpFilter createIpFilterFromConfig(AppConfig.IpFilterConfig config) { IpFilter filter = new IpFilter(); - // Set mode if ("ALLOWLIST".equalsIgnoreCase(config.mode())) { filter.setMode(IpFilter.FilterMode.ALLOWLIST); } else { filter.setMode(IpFilter.FilterMode.BLOCKLIST); } - // Add blocked IPs for (String ip : config.blockedIps()) { filter.addBlockedIp(ip); } - // Add allowed IPs for (String ip : config.allowedIps()) { filter.addAllowedIp(ip); } From c0c9ca6eb06ff392653a53f1e97e9b8e1adb081d Mon Sep 17 00:00:00 2001 From: Eric Thilen Date: Thu, 26 Feb 2026 18:29:26 +0100 Subject: [PATCH 3/3] Address CodeRabbit review - normalize empty path and skip empty query segments --- src/main/java/org/example/ConnectionHandler.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/ConnectionHandler.java b/src/main/java/org/example/ConnectionHandler.java index 42dbe088..d98475a0 100644 --- a/src/main/java/org/example/ConnectionHandler.java +++ b/src/main/java/org/example/ConnectionHandler.java @@ -115,9 +115,10 @@ private void resolveTargetFile(String uri) { // ----------------------------- private static String extractPath(String uri) { - if (uri == null) return "/"; + if (uri == null || uri.isEmpty()) return "/"; int q = uri.indexOf('?'); - return (q >= 0) ? uri.substring(0, q) : uri; + String path = (q >= 0) ? uri.substring(0, q) : uri; + return path.isEmpty() ? "/" : path; } private static Map> parseQueryParams(String uri) { @@ -143,6 +144,11 @@ private static Map> parseQueryParams(String uri) { int equalsIndex = pair.indexOf('='); + if (pair.isEmpty()) { + start = ampIndex + 1; + continue; + } + String key; String value;