Repository navigation
Reject malformed HTTP versions with 400, unsupported ones with 505 - #79
Merged
Merged
Conversation
Since #55 every version the parser does not accept throws 505, whether it is HTTP/2.0 or "http/1.1" or "HTTP/1.x". 505 (RFC 9110 15.6.6) says "I understood the version you asked for and do not implement it"; for bytes that are not an HTTP-version at all that is the wrong answer - the request line is simply malformed, which is a 400. Split the two on the request line and, symmetrically, on the status line: a version that matches the grammar ("HTTP/" DIGIT "." DIGIT, exactly 8 case-sensitive bytes, RFC 9112 2.3) but is not 1.0/1.1 is 505 "Unsupported HTTP version."; anything else in that position is 400 "Invalid HTTP version.". The grammar check is an internal helper next to IsValidHttpVersion, so the public surface does not grow. HttpParseException.IsLimitViolation was `StatusCode != 400`, so since #55 it has reported every version error as a limit breach. It now means exactly 431, and the StatusCode doc lists the three codes in use. Fixes #71.
Kaliumhexacyanoferrat
pushed a commit
to Kaliumhexacyanoferrat/GenHTTP
that referenced
this pull request
Nov 4, 2026
* Update Glyph11 to 0.4.0 and parse with TryParseRequestHeader Glyph11 0.4.0 marks TryExtractFullHeaderValidated and TryExtractFullHeaderROM [Obsolete]: they report one less than the length of the header block, which is why every call site here sliced at bytesRead + 1. TryParseRequestHeader reports the length itself, so the body starts at consumed. With TreatWarningsAsErrors the update does not build without the move, and the rename and the dropped + 1 have to land together - renaming alone would skip the first byte of every body or pipelined request. Both engines, the body lifecycle tests and the benchmark context move. Glyph11.Pico stays at 0.0.1: PicoParser.TryParse keeps its convention, so the Pico path keeps its + 1. Closes #972. * Expect 400 for a malformed HTTP version, 505 for an unsupported one Glyph11 0.4.0 (dotnet-web-stack/Glyph11#79) splits what it used to report as one 505: a version that matches HTTP-version ("HTTP/" DIGIT "." DIGIT) but is not 1.0/1.1 stays a 505, "Unsupported HTTP version.", while bytes that are not an HTTP-version at all make the request line invalid, a 400 (RFC 9112 2.3 and 3). "GET / HTTP/1" has no minor version, so it is malformed rather than unsupported, which is also what http-probe's COMP-VERSION-MISSING-MINOR expects. HTTP/2.0 keeps its 505, under the new message. * Expect a truncated chunked body to hand out its data before failing Glyph11 0.4.0 (dotnet-web-stack/Glyph11#83) hands chunk data out as it arrives instead of waiting for the whole chunk, so a peer that declares a huge chunk can no longer make the connection buffer it. For "5\r\nHel" the first read now returns the three bytes that did arrive, and the truncation surfaces as the 400 ProviderException on the next read - the point where the body actually ends early. * Drop the Glyph11.Pico dependency Glyph11 discontinues Glyph11.Pico and Glyph11.Native (dotnet-web-stack/Glyph11#84): they get no further releases. The Ioxide engine's GENHTTP_IOXIDE_PARSER=pico switch goes with the package, so requests are always parsed by UltraHardenedParser, and the body lifecycle tests that need unvalidated framing headers get them from Glyph11's FlexibleParser instead of picohttpparser.
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 #71
Why
Since #55 every version the parser does not accept throws 505, whether it is
HTTP/2.0,http/1.1orHTTP/1.x. 505 (RFC 9110 §15.6.6) means "I understood the version and do not implement it". For bytes that are not an HTTP-version at all, that is the wrong answer: the request line is malformed, which is a 400.The issue proposes one rule for both implementations. The C core and the bindings are no longer supported, so its C half is moot. This PR applies the rule to the managed parser.
What changes
"HTTP/" DIGIT "." DIGIT, exactly 8 case-sensitive bytes, RFC 9112 §2.3) but is not 1.0/1.1 throws 505Unsupported HTTP version.. Anything else in that position throws 400Invalid HTTP version..internalhelper,ParserConstants.IsWellFormedHttpVersion, next toIsValidHttpVersion. The public API does not grow.HttpParseException.IsLimitViolationwasStatusCode != 400, so since Yield error 505 on unsupported HTTP version #55 it reported every version error as a limit breach. It now means exactlyStatusCode == 431. TheStatusCodedoc lists the three codes in use.IsValidHttpVersiondoc.Decisions worth reviewing
Higher minor versions (
HTTP/1.2) stay rejected, now as 505. RFC 9110 §2.5 says a recipient SHOULD process a higher minor version as the highest one it implements. The parser has always refused anything but 1.0/1.1, and this PR keeps that policy and only changes the code it reports. Accepting 1.x as 1.1 would be a separate change.The status line gets the same rule. For a client, the code is a classification rather than something to send back. The response parser already reported 505, so this keeps both directions consistent.
IsLimitViolationnow means 431 and nothing else. A caller that builds its ownHttpParseExceptionwith some other non-400 code would see it flip tofalse. The library itself throws only 400, 431 and 505.http-probe agrees with the split.
COMP-VERSION-MISSING-MINOR(GET / HTTP/1) expects exactly 400, which the parser fails today with 505.RFC9112-2.3-INVALID-VERSION(HTTP/9.9) accepts 400 or 505, andCOMP-HTTP12-VERSION(HTTP/1.2) accepts 505. Both are unchanged.Downstream: GenHTTP pins the current behaviour in two tests.
ComplianceTests.TestNoMinorHttpVersionexpects 505 forHTTP/1. With this PR that request gets 400.WireTests.TestUnsupportedProtocolVersionmatches the messageInvalid HTTP versionforHTTP/2.0. With this PR the message for that case isUnsupported HTTP version..HTTP/1gets 400 on Kestrel and Ioxide too.Overlap with sibling PRs
UltraHardenedParser.FullResponse.ROM.cs, about 20 lines below this change. They are separate hunks.UltraHardenedParser.FullHeader.ROM.csin other places.Tests
tests/Tests/UltraHardenedParser.VersionStatus.cs, 55 cases. Requests and responses, single- and multi-segment:HTTP/2.0,HTTP/1.2,HTTP/0.9,HTTP/3.0throw 505.http/1.1,HTTP/1.x,HTTP1.1,HTTP/11,HTTP/1.1x,HTTP/1.,HTTP/throw 400.HTTP/1.0andHTTP/1.1still parse.IsLimitViolationis false for 400 and 505 and true for 431.HTTP/9.9, which is well-formed and stays 505.dotnet test -f net10.0: 458 passed (403 before), 0 failed.dotnet build src/Glyph11.slnbuilds clean, with no new warnings.core/diff(not maintained, run for information): divergences drop from 865 to 45 per 200k inputs. All 45 are well-formed unsupported versions, which the C core still reports as 400.