8000 fix: align RE_COOKIE_OCTET with RFC 6265 §4.1.1 to exclude backslash by kawasima · Pull Request #82 · enkan/enkan · GitHub
[go: up one dir, main page]

Skip to content
Merged
8000
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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Improvement proposals and architectural decisions are tracked as GitHub Issues.

Apply this checklist both when reviewing others' code **and** before submitting your own changes.

- Verify all branches for malformed/invalid input, not just the happy path.
- Verify all branches for malformed/invalid input, not just the happy path. **This applies to tests too**: confirm that each test actually exercises the code path it claims — e.g. don't percent-encode chars when testing a regex that runs on the raw string.
- When multiple parsing strategies exist (e.g. quoted vs. unquoted), ensure malformed input in one strategy does not silently fall through to another and produce a wrong result.
- When extending the scope of a utility function (e.g. adding new types to a filter), trace all call sites to verify that downstream behavior is correct for the new inputs — not just the entry point.
- String comparisons against HTTP header values (media types, field names) must be case-insensitive per the relevant RFC. Verify that `equalsIgnoreCase`, `toLowerCase(Locale.ROOT)`, or `regionMatches(true, ...)` is used appropriately.
Expand Down
10 changes: 7 additions & 3 deletions enkan-web/src/main/java/enkan/middleware/CookiesMiddleware.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@
*/
@Middleware(name = "cookies")
public class CookiesMiddleware implements WebMiddleware {
private static final Pattern RE_COOKIE_OCTET = Pattern.compile("[!#$%&'()*+\\-./0-9:<=>?@A-Z\\[\\]\\^_`a-z\\{\\|\\}~]");
private static final Pattern RE_COOKIE_VALUE = Pattern.compile("\"" + RE_COOKIE_OCTET.pattern() + "*\"|" + RE_COOKIE_OCTET.pattern() + "*");
private static final Pattern RE_COOKIE = Pattern.compile("\\s*(" + RE_TOKEN + ")=(" + RE_COOKIE_VALUE.pattern() + ")\\s*[;,]?");
// RFC 6265 §4.1.1: cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
// Backslash (%x5C) and double-quote (%x22) are explicitly excluded.
private static final Pattern RE_COOKIE_OCTET = Pattern.compile("[\\x21\\x23-\\x2B\\x2D-\\x3A\\x3C-\\x5B\\x5D-\\x7E]");
Comment on lines +25 to +27
Copilot AI Mar 8, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old RE_COOKIE_OCTET character class did not appear to include a literal backslash (it only used backslashes to escape [, ], ^, {, |, } inside the class). As a result, this change may be a no-op with respect to excluding \\, and the new test might already have passed before this PR. Consider adjusting the PR description/rationale (and/or adding a regression test that would fail with the previous regex if there is a concrete behavior difference you’re targeting).

Copilot uses AI. Check for mistakes.
private static final Pattern RE_COOKIE_VALUE = Pattern.compile("\"" + RE_COOKIE_OCTET.pattern() + "*\"|" + RE_COOKIE_OCTET.pattern() + "*");
// Require the value to be followed by a valid delimiter or end-of-input so that
// non cookie-octet chars after the value (e.g. backslash) prevent a match entirely.
private static final Pattern RE_COOKIE = Pattern.compile("\\s*(" + RE_TOKEN + ")=(" + RE_COOKIE_VALUE.pattern() + ")\\s*(?:[;,]|$)");

/**
* Strip quotes from argument string.
Expand Down
83A7
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,35 @@ void parse() {
request.getHeaders().put("Cookie", "A=%E3%81%82%E3%81%84%E3%81%86; B=1");
middleware.handle(request, chain);
}

@Test
void parsesRfcValidCookieOctets() {
// RFC 6265 §4.1.1: spot-check one char from each allowed range using raw octets.
// %x21=!, %x23=#, %x2D=-, %x3A=:, %x3C=<, %x5B=[, %x5D=], %x7E=~
// Chars that have special meaning for form-decoding (+ as space, % as escape) are omitted.
final String expectedValue = "!#-:<[]~";
MiddlewareChain<HttpRequest, HttpResponse, ?, ?> chain = new DefaultMiddlewareChain<>(new AnyPredicate<>(), null,
(Endpoint<HttpRequest, HttpResponse>) req -> {
Cookie cookie = req.getCookies().get("A");
assertThat(cookie).isNotNull();
assertThat(cookie.getValue()).isEqualTo(expectedValue);
return HttpResponse.of("ok");
});
request.getHeaders().put("Cookie", "A=!#-:<[]~");
middleware.handle(request, chain);
}

@Test
void backslashIsNotConsumedAsCookieOctet() {
// Backslash (%x5C) is not a valid cookie-octet (RFC 6265 §4.1.1).
// RE_COOKIE requires the value to be followed by a delimiter or end-of-input,
// so BAD=\ (a trailing backslash) does not match and the cookie is not parsed at all.
MiddlewareChain<HttpRequest, HttpResponse, ?, ?> chain = new DefaultMiddlewareChain<>(new AnyPredicate<>(), null,
(Endpoint<HttpRequest, HttpResponse>) req -> {
assertThat(req.getCookies()).doesNotContainKey("BAD");
return HttpResponse.of("ok");
});
request.getHeaders().put("Cookie", "BAD=\\");
middleware.handle(request, chain);
Comment on lines +85 to +95
Copilot AI Mar 8, 2026
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test asserts that a backslash in the Cookie header produces a cookie with an empty value. That outcome is a side effect of RE_COOKIE_VALUE matching the empty string (via *) and RE_COOKIE not requiring a delimiter/end-of-header after the value, so the parser silently truncates at BAD= rather than rejecting the malformed cookie-pair. If the goal is “backslash is not accepted as cookie-octet”, consider asserting that BAD is not present (or tightening the cookie-pair regex so non cookie-octets prevent a match) instead of locking in the truncation behavior.

Copilot uses AI. Check for mistakes.
}
}
0