Skip to content

give ParsingErrorHandler access to the request that was rejected - #1246

Open
pjfanning wants to merge 1 commit into
apache:mainfrom
pjfanning:illegal-request-context
Open

give ParsingErrorHandler access to the request that was rejected#1246
pjfanning wants to merge 1 commit into
apache:mainfrom
pjfanning:illegal-request-context

Conversation

@pjfanning

Copy link
Copy Markdown
Member

Fixes #1245.

ParsingErrorHandler is the extension point for the response to a request that fails to parse, but none of its four arguments describe the request that was rejected: a custom handler can branch on the status code and on ErrorInfo, and cannot recover the method or the request target. The information is not lost anywhere upstream either — ParserOutput.MessageStartError carries only (status, info), so it is discarded at the point where the parser gives up.

What this adds

final class IllegalRequestContext private[http] (
    val method: Option[HttpMethod],
    val rawRequestTarget: Option[String],
    val protocol: Option[HttpProtocol])

abstract class ParsingErrorHandler {
  def handle(status: StatusCode, error: ErrorInfo, log: LoggingAdapter,
             settings: ServerSettings): HttpResponse

  def handle(status: StatusCode, error: ErrorInfo, log: LoggingAdapter,
             settings: ServerSettings, context: IllegalRequestContext): HttpResponse =
    handle(status, error, log, settings)
}

ControllerStage calls the five-argument overload. The default implementation of it delegates to the existing four-argument method, and DefaultParsingErrorHandler still implements only that one, so existing handlers — and anything matching the four-argument signature — keep working unchanged.

Every field is optional because a request can be rejected before that part of it was read: an unsupported method fails before the request target is seen, an unparsable request target fails before the protocol is seen. rawRequestTarget is deliberately the raw bytes of the request target, which is attacker-controlled and malformed whenever it caused the rejection; the scaladoc says so.

Internals

  • ParserOutput.MessageStartError (@InternalApi) gains a context field, defaulted to IllegalRequestContext.empty.
  • HttpMessageParser gains an overridable illegalRequestContext that failMessageStart and the message-start completion handling pass along; only the request parser populates it.
  • HttpRequestParser clears method/uri/uriBytes at the start of each message and tracks whether the protocol belongs to the message being parsed. The parser instance is reused across a keep-alive connection and those fields were never reset, so populating the context at the failure site is what keeps a rejection from reporting the previous request's values.
  • HttpServerBluePrint.establishAbsoluteUri fills the context for the two rejections it raises itself (CONNECT, and a Host header that does not match an absolute request target).

Tests

RequestParserSpec asserts the context for an illegal request target, for a rejection after the request line parsed, for a rejection before the method was known, and that nothing leaks from the previous request on the same connection. HttpServerSpec asserts end to end that a handler overriding the five-argument method sees the method, target and protocol, while the existing test for a handler that overrides only the four-argument method still passes unchanged.

🤖 Generated with Claude Code

@pjfanning
pjfanning force-pushed the illegal-request-context branch from 6fd107e to fe874c7 Compare August 27, 2026 20:03
…che#1245)

`ParsingErrorHandler` could not see the request it was rejecting: none of
its four arguments describe the method or the request target, and
`ParserOutput.MessageStartError` discarded both at the point where the
parser gave up.

Add `IllegalRequestContext` and a five-argument `handle` overload that
receives it. The overload defaults to the existing four-argument method,
so existing handlers, `DefaultParsingErrorHandler` included, keep working
unchanged.

The parser populates the context at the failure site: its `method`,
`uri` and `uriBytes` fields are reused across a keep-alive connection, so
they are cleared for every message to stop a rejection from reporting the
previous request's values.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@pjfanning
pjfanning force-pushed the illegal-request-context branch from fe874c7 to c9560d6 Compare August 27, 2026 20:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ParsingErrorHandler cannot see the request that was rejected

1 participant