-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/url redirect filter #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Ericthilen
wants to merge
78
commits into
main
Choose a base branch
from
feature/url-redirect-filter
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
78 commits
Select commit
Hold shift + click to select a range
696e1bc
updated pom.xml
Ericthilen de1b6cf
Add HttpRequest (method, path)
Ericthilen e21bc8c
Add FilterChain to execute Httpfilter pipeline
Ericthilen 78e8df0
Add HttpResponse (status + headers + setHeader)
Ericthilen 0ed2a79
Add RedirectRule (pattern match, targetUrl, 301/302
Ericthilen 3e4c746
Add RedirectRulesLoader.compileSourcePattern (wildcard support
Ericthilen 1a5ce4d
Add RedirectFilter tests (301/302/no match/wildcard)
Ericthilen f3f39b3
Add RedirectResponse DTO (location + statusCode)
Ericthilen 699c9d7
Add RedirectFilter (set Location + stop chain + logging)
Ericthilen f48524b
Add RedirectFilter (set Location + stop chain + logging)
Ericthilen 37a8231
feat: improve redirect rule parsing and validation
Ericthilen 30f3b6c
refactor(server): extract TerminalHandler to own file
Ericthilen 5fbd3db
fix(server): make '*' not match '/' in redirect wildcards; update test
Ericthilen b06171e
extract TerminalHandler from FilterChain into a interface
Ericthilen 172671c
make RedirectRulesLoader wildcard '*' not match '/' (avoid matching s…
Ericthilen 6513e43
removed unused RedirectResponse and .gitkeep placeholders
Ericthilen 101d1f4
Update redirect rules and tests
viktorlindell12 aebd6de
Merge branch 'Url-Redirect-Filter-Update' into URL-Redirect-Filter
viktorlindell12 5fdac73
Update RedirectFilter.java
viktorlindell12 e90052d
Update RedirectRule.java
viktorlindell12 bb1caff
Update RedirectFilter.java
viktorlindell12 23df773
Update RedirectRulesLoader.java
viktorlindell12 ad54bc9
Update RedirectFilterTest.java
viktorlindell12 1d1f881
Delete pom.xml
viktorlindell12 ab4d5ef
Delete src/test/java/org/example/server/RedirectFilterTest.java
viktorlindell12 b7c5eda
Delete src/main/java/org/example/server/HttpResponse.java
viktorlindell12 8d348a0
Delete src/main/java/org/example/server/HttpRequest.java
viktorlindell12 ed50be1
Delete src/main/java/org/example/server/FilterChain.java
viktorlindell12 1f857d2
Delete src/main/java/org/example/server/HttpFilter.java
viktorlindell12 047e9c1
Delete src/main/java/org/example/server/TerminalHandler.java
viktorlindell12 ed9c02f
Delete src/main/java/org/example/server/RedirectRule.java
viktorlindell12 7e35661
Delete src/main/java/org/example/server/RedirectFilter.java
viktorlindell12 9b041a8
Delete src/main/java/org/example/server/RedirectRulesLoader.java
viktorlindell12 f40e141
Add redirect filter rules and tests
viktorlindell12 48bc5f8
Merge remote-tracking branch 'origin/main' into URL-Redirect-Filter
viktorlindell12 ebfe583
Restore pom.xml from main
viktorlindell12 7ad19c7
Merge remote-tracking branch 'origin/main' into feature/url-redirect-…
viktorlindell12 5be82bf
updated pom.xml
Ericthilen a44524f
Add HttpRequest (method, path)
Ericthilen 0121f64
Add FilterChain to execute Httpfilter pipeline
Ericthilen ffb5c1c
Add HttpResponse (status + headers + setHeader)
Ericthilen 757e94d
Add RedirectRule (pattern match, targetUrl, 301/302
Ericthilen 4bf1427
Add RedirectRulesLoader.compileSourcePattern (wildcard support
Ericthilen 06266b4
Add RedirectFilter tests (301/302/no match/wildcard)
Ericthilen d4011bc
Add RedirectResponse DTO (location + statusCode)
Ericthilen 3574f08
Add RedirectFilter (set Location + stop chain + logging)
Ericthilen 0f133da
Add RedirectFilter (set Location + stop chain + logging)
Ericthilen 0abfe57
feat: improve redirect rule parsing and validation
Ericthilen 6420cab
refactor(server): extract TerminalHandler to own file
Ericthilen 2c03c8f
fix(server): make '*' not match '/' in redirect wildcards; update test
Ericthilen 831b1d8
extract TerminalHandler from FilterChain into a interface
Ericthilen 43f0db2
make RedirectRulesLoader wildcard '*' not match '/' (avoid matching s…
Ericthilen 3afa435
removed unused RedirectResponse and .gitkeep placeholders
Ericthilen a5cf986
Update redirect rules and tests
viktorlindell12 d85ea46
Update RedirectFilter.java
viktorlindell12 5e34302
Update RedirectRule.java
viktorlindell12 96843ae
Update RedirectFilter.java
viktorlindell12 45d2171
Update RedirectRulesLoader.java
viktorlindell12 5d36ee2
Update RedirectFilterTest.java
viktorlindell12 c62ed08
Delete pom.xml
viktorlindell12 ab6a8d7
Delete src/test/java/org/example/server/RedirectFilterTest.java
viktorlindell12 05dfe82
Delete src/main/java/org/example/server/HttpResponse.java
viktorlindell12 76d5bd2
Delete src/main/java/org/example/server/HttpRequest.java
viktorlindell12 4f6fd85
Delete src/main/java/org/example/server/FilterChain.java
viktorlindell12 8e29d15
Delete src/main/java/org/example/server/HttpFilter.java
viktorlindell12 0428e7e
Delete src/main/java/org/example/server/TerminalHandler.java
viktorlindell12 3ff5c22
Delete src/main/java/org/example/server/RedirectRule.java
viktorlindell12 371d28d
Delete src/main/java/org/example/server/RedirectFilter.java
viktorlindell12 7bd0b82
Delete src/main/java/org/example/server/RedirectRulesLoader.java
viktorlindell12 da8c1d5
Add redirect filter rules and tests
viktorlindell12 ae73793
Restore pom.xml from main
viktorlindell12 7c5d1d3
Update RedirectRule.java
viktorlindell12 3bd0419
Update RedirectRule.java
viktorlindell12 f63637b
Extend header-injection guard to null-byte characters. Fixed issue fr…
Ericthilen 838a917
Update RedirectRule.java
viktorlindell12 30fba9c
Update RedirectRule.java
viktorlindell12 c68a99f
Merge remote-tracking branch 'origin/main' into feature/url-redirect-…
viktorlindell12 2ca399f
Merge remote-tracking branch 'origin/feature/url-redirect-filter' int…
viktorlindell12 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
54 changes: 54 additions & 0 deletions
54
src/main/java/org/example/filter/redirect/RedirectFilter.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| package org.example.filter.redirect; | ||
|
|
||
| import org.example.filter.Filter; | ||
| import org.example.filter.FilterChain; | ||
| import org.example.http.HttpResponseBuilder; | ||
| import org.example.httpparser.HttpRequest; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Objects; | ||
| import java.util.logging.Logger; | ||
|
|
||
| public final class RedirectFilter implements Filter { | ||
| private static final Logger LOG = Logger.getLogger(RedirectFilter.class.getName()); | ||
| private final List<RedirectRule> rules; | ||
|
|
||
| public RedirectFilter(List<RedirectRule> rules) { | ||
| this.rules = List.copyOf(Objects.requireNonNull(rules, "rules")); | ||
| } | ||
|
|
||
| @Override | ||
| public void init() { | ||
| // no-op | ||
| } | ||
|
|
||
| @Override | ||
| public void doFilter(HttpRequest request, HttpResponseBuilder response, FilterChain chain) { | ||
| String path = request.getPath(); | ||
|
|
||
| if (path == null) { | ||
| chain.doFilter(request, response); | ||
| return; | ||
| } | ||
|
|
||
| for (RedirectRule rule : rules) { | ||
| if (rule.matches(path)) { // <-- DENNA RADEN SKA FINNAS | ||
| final String sanitizedPath = path.replaceAll("[\\r\\n]", "_"); | ||
| LOG.info(() -> "Redirecting " + sanitizedPath + " -> " | ||
| + rule.getTargetUrl() + " (" + rule.getStatusCode() + ")"); | ||
|
|
||
| response.setStatusCode(rule.getStatusCode()); | ||
| response.setHeader("Location", rule.getTargetUrl()); | ||
| return; // STOP pipeline | ||
| } | ||
| } | ||
|
|
||
| chain.doFilter(request, response); | ||
| } | ||
|
|
||
|
|
||
| @Override | ||
| public void destroy() { | ||
| // no-op | ||
| } | ||
| } |
44 changes: 44 additions & 0 deletions
44
src/main/java/org/example/filter/redirect/RedirectRule.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package org.example.filter.redirect; | ||
|
|
||
| import java.util.Objects; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| public final class RedirectRule { | ||
| private final Pattern sourcePattern; | ||
| private final String targetUrl; | ||
| private final int statusCode; | ||
|
|
||
| public RedirectRule(Pattern sourcePattern, String targetUrl, int statusCode) { | ||
| this.sourcePattern = Objects.requireNonNull(sourcePattern, "sourcePattern"); | ||
| this.targetUrl = Objects.requireNonNull(targetUrl, "targetUrl"); | ||
| if (this.targetUrl.isBlank()) { | ||
| throw new IllegalArgumentException("targetUrl must not be blank"); | ||
| } | ||
| if (this.targetUrl.indexOf('\r') >= 0 || this.targetUrl.indexOf('\n') >= 0 | ||
| || this.targetUrl.indexOf('\0') >= 0) { | ||
| throw new IllegalArgumentException("targetUrl must not contain CR/LF or null byte"); | ||
| } | ||
|
|
||
| if (statusCode != 301 && statusCode != 302) { | ||
| throw new IllegalArgumentException("statusCode must be 301 or 302"); | ||
| } | ||
| this.statusCode = statusCode; | ||
| } | ||
|
|
||
| public Pattern getSourcePattern() { return sourcePattern; } | ||
| public String getTargetUrl() { return targetUrl; } | ||
| public int getStatusCode() { return statusCode; } | ||
|
|
||
| public boolean matches(String requestPath) { | ||
| return requestPath != null && sourcePattern.matcher(requestPath).matches(); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "RedirectRule{" + | ||
| "sourcePattern=" + sourcePattern + | ||
| ", targetUrl='" + targetUrl + '\'' + | ||
| ", statusCode=" + statusCode + | ||
| '}'; | ||
| } | ||
| } | ||
46 changes: 46 additions & 0 deletions
46
src/main/java/org/example/filter/redirect/RedirectRulesLoader.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| package org.example.filter.redirect; | ||
|
|
||
| import java.util.regex.Pattern; | ||
|
|
||
| public final class RedirectRulesLoader { | ||
| private static final String REGEX_PREFIX = "regex:"; | ||
|
|
||
| private RedirectRulesLoader() {} | ||
|
|
||
| public static Pattern compileSourcePattern(String sourcePath) { | ||
| if (sourcePath == null || sourcePath.isBlank()) { | ||
| throw new IllegalArgumentException("sourcePath must not be blank"); | ||
| } | ||
|
|
||
| String trimmed = sourcePath.trim(); | ||
|
|
||
| if (trimmed.startsWith(REGEX_PREFIX)) { | ||
| String rawRegex = trimmed.substring(REGEX_PREFIX.length()).strip(); | ||
| if (rawRegex.isBlank()) { | ||
| throw new IllegalArgumentException("regex sourcePath must not be blank"); | ||
| } | ||
| return Pattern.compile(rawRegex); | ||
| } | ||
|
|
||
| String regex; | ||
| if (trimmed.contains("*")) { | ||
| regex = wildcardToRegex(trimmed); | ||
| } else { | ||
| regex = Pattern.quote(trimmed); | ||
| } | ||
| return Pattern.compile("^" + regex + "$"); | ||
| } | ||
|
|
||
| private static String wildcardToRegex(String wildcard) { | ||
| StringBuilder sb = new StringBuilder(); | ||
| for (int i = 0; i < wildcard.length(); i++) { | ||
| char c = wildcard.charAt(i); | ||
| if (c == '*') { | ||
| sb.append("[^/]*"); | ||
| } else { | ||
| sb.append(Pattern.quote(String.valueOf(c))); | ||
| } | ||
| } | ||
| return sb.toString(); | ||
| } | ||
| } |
Empty file.
109 changes: 109 additions & 0 deletions
109
src/test/java/org/example/filter/redirect/RedirectFilterTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| package org.example.filter.redirect; | ||
|
|
||
| import org.example.filter.FilterChain; | ||
| import org.example.http.HttpResponseBuilder; | ||
| import org.example.httpparser.HttpRequest; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
|
|
||
| class RedirectFilterTest { | ||
|
|
||
| private static HttpRequest request(String path) { | ||
| return new HttpRequest("GET", path, "HTTP/1.1", Map.of(), null); | ||
| } | ||
|
|
||
| private static String responseAsString(HttpResponseBuilder response) { | ||
| return new String(response.build(), StandardCharsets.UTF_8); | ||
| } | ||
|
|
||
| @Test | ||
| void returns_301_redirect_and_stops_pipeline() { | ||
| RedirectFilter filter = new RedirectFilter(List.of( | ||
| new RedirectRule(Pattern.compile("^/old-page$"), "/new-page", 301) | ||
| )); | ||
|
|
||
| AtomicBoolean chainCalled = new AtomicBoolean(false); | ||
| FilterChain chain = (req, res) -> chainCalled.set(true); | ||
|
|
||
| HttpResponseBuilder res = new HttpResponseBuilder(); | ||
|
|
||
| filter.doFilter(request("/old-page"), res, chain); | ||
|
|
||
| String raw = responseAsString(res); | ||
| assertThat(raw).contains("HTTP/1.1 301 Moved Permanently"); | ||
| assertThat(raw).contains("Location: /new-page"); | ||
| assertThat(chainCalled.get()).isFalse(); | ||
| } | ||
|
|
||
| @Test | ||
| void returns_302_redirect_and_stops_pipeline() { | ||
| RedirectFilter filter = new RedirectFilter(List.of( | ||
| new RedirectRule(Pattern.compile("^/temp$"), "https://example.com/temporary", 302) | ||
| )); | ||
|
|
||
| AtomicBoolean chainCalled = new AtomicBoolean(false); | ||
| FilterChain chain = (req, res) -> chainCalled.set(true); | ||
|
|
||
| HttpResponseBuilder res = new HttpResponseBuilder(); | ||
|
|
||
| filter.doFilter(request("/temp"), res, chain); | ||
|
|
||
| String raw = responseAsString(res); | ||
| assertThat(raw).contains("HTTP/1.1 302 Found"); | ||
| assertThat(raw).contains("Location: https://example.com/temporary"); | ||
| assertThat(chainCalled.get()).isFalse(); | ||
| } | ||
|
|
||
| @Test | ||
| void no_matching_rule_calls_next_in_chain() { | ||
| RedirectFilter filter = new RedirectFilter(List.of( | ||
| new RedirectRule(Pattern.compile("^/old-page$"), "/new-page", 301) | ||
| )); | ||
|
|
||
| AtomicBoolean chainCalled = new AtomicBoolean(false); | ||
| FilterChain chain = (req, res) -> { | ||
| chainCalled.set(true); | ||
| res.setStatusCode(200); | ||
| res.setBody("terminal"); | ||
| }; | ||
|
|
||
| HttpResponseBuilder res = new HttpResponseBuilder(); | ||
|
|
||
| filter.doFilter(request("/nope"), res, chain); | ||
|
|
||
| String raw = responseAsString(res); | ||
| assertThat(chainCalled.get()).isTrue(); | ||
| assertThat(raw).contains("HTTP/1.1 200 OK"); | ||
| assertThat(raw).doesNotContain("Location:"); | ||
| } | ||
|
|
||
| @Test | ||
| void wildcard_matching_docs_star() { | ||
| var p = RedirectRulesLoader.compileSourcePattern("/docs/*"); | ||
| assertThat(p.matcher("/docs/test").matches()).isTrue(); | ||
| assertThat(p.matcher("/docs/any/path").matches()).isFalse(); | ||
| assertThat(p.matcher("/doc/test").matches()).isFalse(); | ||
| } | ||
|
|
||
| @Test | ||
| void regex_matching_via_loader_prefix() { | ||
| var p = RedirectRulesLoader.compileSourcePattern("regex:^/docs/(v1|v2)$"); | ||
| assertThat(p.matcher("/docs/v1").matches()).isTrue(); | ||
| assertThat(p.matcher("/docs/v2").matches()).isTrue(); | ||
| assertThat(p.matcher("/docs/v3").matches()).isFalse(); | ||
| } | ||
|
|
||
| @Test | ||
| void redirect_rule_rejects_invalid_status_code() { | ||
| assertThatThrownBy(() -> new RedirectRule(Pattern.compile("^/x$"), "/y", 307)) | ||
| .isInstanceOf(IllegalArgumentException.class); | ||
| } | ||
| } |
Empty file.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.