Repository navigation
Fix codebase review findings across routing, servlet, multipart and server - #55
Merged
Merged
Conversation
- HttpContext.status rejects codes outside 200-599 (0, 101, 1000 broke the status line or the connection) - HEAD responses carry the Content-Length of the buffered GET body - Server logs the raw, control-char-escaped path on errors, so a decoded %0A can't forge log lines - Opt-in streaming: HttpContext.stream() / Turismo.stream() send status and headers and return a chunked body stream; status/header changes afterwards throw IllegalStateException, and errors after streaming are logged and end the response - Validation.validateLocation rejects all control characters; new Validation.isLocalPath and Turismo.redirectLocal guard against open redirects - Tests for the above, plus redirect(307, ...) and CR/LF rejection through the embedded server Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qjn6ZdPkHxbqCyPby4Lu4V
- Store text fields as lists (linear time for repeated names) and cap the number of parts (MultipartParser.setMaxParts, default 1000; exceeding it throws ContentTooLargeException, answered with 413). - Keep file parts apart from text fields and support several files per name via Parametrizable.addFile, FilePart, MultipartRequest.getFile, getFiles and getFileNames. The first file still backs the legacy [contentType, fileName] parameter and byte[] attribute. - Merge the wrapped request's (query string) parameters with body fields, query values first. - Treat backslashes in quoted parameters literally and decode only %22, %0D and %0A, as browsers send them; document file names as untrusted. - Parse over ByteArrayOutputStream's buffer instead of copying it and decode text fields in place; only file contents are copied. - Add tests, including MultipartFilter 400/413 responses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qjn6ZdPkHxbqCyPby4Lu4V
- release.yml: job-level contents: write only, checkout with persist-credentials: false, GPG passphrase via MAVEN_GPG_PASSPHRASE (read natively by maven-gpg-plugin 3.2.8), GitHub release created as a draft since Central publishing is manual - README: open-redirect warning and redirectLocal, streaming responses, status range and HEAD Content-Length, JDK server timeout properties, updated release steps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qjn6ZdPkHxbqCyPby4Lu4V
- Bind InputStream arguments after all other arguments (declared order kept for the call), so form parameters can still be read; a form read after the raw body is taken is now a 400 RequestException instead of an IllegalStateException. - Detect subclass overrides by name and parameter types, and never treat a package-private method from another package as overridden, so an overload no longer hides the superclass's route. - Make controller() atomic: build and validate every route, then register them only if all succeed. - Treat trailing and empty path segments as significant for pattern routes (split with -1), consistent with exact routes; :name and * no longer match an empty segment. Add PathPattern.split(). - Re-registering the same method and pattern replaces the old route in place (last wins, like exact routes), serialized for thread safety. - Resolve parameter names of an abstract route method from its concrete implementation; precise error when unavailable. - Answer OPTIONS automatically with 204 and an Allow header when no OPTIONS route matches; list OPTIONS in 405 Allow headers too. - Reject NaN, Infinity, hex floats, type suffixes and overflow for double/float arguments (400). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qjn6ZdPkHxbqCyPby4Lu4V
A target taken from the request that points off-site is a bad request, not a server error. Also correct form()'s Javadoc, which now fails with 400 after the raw body was taken. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qjn6ZdPkHxbqCyPby4Lu4V
Match App's routing in the servlet-side resolvers: - A request whose raw URI contains %2F is resolved against the raw path's segments, each percent-decoded on its own, instead of the container-decoded path info. /admin%2Fsecret no longer reaches an /admin/secret route; /files/:name matches /files/a%2Fb with name=a/b. HEAD fallback and 405 + Allow apply to these requests too. Custom MethodPathResolver subclasses get 404 for them unless they override the new resolveEncoded() hook. - ListResolver and MapResolver use concurrent collections, so routes can be registered while requests are resolved. - MapResolver rejects null paths and actions; ListResolver rejects a null method (such routes could never match). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qjn6ZdPkHxbqCyPby4Lu4V
- Servlet.service saves the thread's Env and restores it afterwards (new Env.restore), so a forward/alias/jsp that re-enters the servlet no longer leaves the forwarding action without an Env. - Exceptions thrown by an action (including ActionException) are logged via System.Logger and answered with 500, or rethrown as a ServletException when the response is already committed, instead of reaching the container's error page. - Requests without a declared charset are decoded as UTF-8, like the embedded server. - Alias.forward rejects targets not starting with '/' with an ActionException; Javadoc warns against building targets from request input and explains the servlet mapping jsp() needs. - ClassForName trims the name, loads without initializing until the type check passes, and wraps LinkageErrors (failed static init, missing classes) in ClassForNameException. - Tests for Servlet.service, Env.restore, the action behaviors and ClassForName edge cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qjn6ZdPkHxbqCyPby4Lu4V
The web.xml example mapped turismo to /*, which also matches JSP paths, so jsp() forwarded back into turismo and got 404. Map to / instead and explain the options. Add an ExtendedRoutesMap section (RoutesMap has no string alias), a complete JSP example, missing imports, and the servlet backend's error, charset and encoded-slash behavior. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qjn6ZdPkHxbqCyPby4Lu4V
- Split servlet paths with PathPattern.split so trailing slashes are significant there too, as in App. - An unalignable encoded path matched the root route after the split change; use an explicit no-match sentinel instead. - Answer OPTIONS with 204 and Allow, and list OPTIONS in 405 Allow headers, as App does. - List this round's behavior changes under Upgrading to 5.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qjn6ZdPkHxbqCyPby4Lu4V
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Fixes the findings from a full codebase review. Each fix has a regression test. The test count goes from 243 to 331, and
mvn verifypasses, including the javadoc build.Security and robustness
MultipartParser.setMaxParts).redirect()now rejects every control character, including TAB.redirectLocal()andValidation.isLocalPath()block open redirects such as//evil.com,/\evil.comandhttps://…. A target that isn't a local path gets a 400.%0Ain a URL can't forge log lines.ClassForName: a class is no longer initialized before its type is checked,LinkageErrors are wrapped, and the class name is trimmed.persist-credentials: false.MAVEN_GPG_PASSPHRASE.Correctness
Servlet.service()saves and restores the thread-localEnv. Before, aforward()/alias()back into turismo broke the outer action.InputStreamarguments are bound after the others. Before,(InputStream in, String name)gave a 500.controller():Appand the servletListResolver.Allow, and 405Allowheaders list OPTIONS.getFile,getFiles,getFileNames), kept separate from text fields.Appdoes.App.Content-Length.Alias: the forward target is validated.New API
Turismo.stream()/HttpContext.stream(): opt-in chunked streaming, embedded server only.Turismo.redirectLocal(...)andValidation.isLocalPath(...).FilePart,MultipartRequest.getFile/getFiles/getFileNames, andMultipartParser.setMaxParts.Env.restore(Env), plus protected hooks onMethodPathResolver.Docs
/instead of/*, which forwarded JSPs back into turismo and gave a 404.ExtendedRoutesMapsection.Behavior changes to note
/users/:idno longer matches/users/42/.Allowheaders now include OPTIONS.status()throws for codes outside 200–599.\"is no longer decoded;%22is.Not changed
jsp()still forwards by path rather than through the namedjspservlet: a named forward keeps the request path, which Jasper would try to render instead of the JSP file.🤖 Generated with Claude Code
https://claude.ai/code/session_01Qjn6ZdPkHxbqCyPby4Lu4V
Generated by Claude Code