httpparse
nimony/lib/std/http/httpparse.nim
const ParseIncomplete: int64Not enough bytes yet. Read more and ask again; nothing was consumed.
const ParseBad: int64Malformed. HTTP/1.1 framing cannot resynchronize, so the only correct response is to answer 400 and close.
const MaxHeadLen: int64total bytes of request line + headers
const MaxHeaderCount: int64headers per message
const MaxTargetLen: int64request target
const MaxValueLen: int64a single header value
proc skipSpaces(buf: openArray): int64Past any spaces and tabs. Never fails; may return
0.proc parseCrLf(buf: openArray): int64Past a CRLF. A bare LF is accepted because too much of the world emits it; a bare CR is not, since it is the classic request-smuggling seam.
proc parseToken(buf: openArray): int64The length of the leading run of
tchar, which is also the token: it isbuf's firstresultbytes.proc parseUntil(buf: openArray; stop: char): int64The length up to but not including
stop.proc parseUntilEol(buf: openArray): int64The length up to but not including the CR or LF that ends the line.
proc parseMethod(tags: ref HttpTags.Obj not nil; buf: openArray; meth: var TagId): int64A method name followed by one space.
proc parseVersion(buf: openArray; v: var TagId): int64HTTP/1.1,HTTP/1.0, orHTTP/2.proc parseRequestLine(buf: openArray; m: var HttpMsg): int64METHOD SP target SP HTTP/1.1 CRLF, opening the message node.proc parseHeaderLine(buf: openArray; m: var HttpMsg): int64One
name ":" OWS value OWS CRLF, appended tom.proc parseStatus(buf: openArray; status: var int64): int64Exactly three digits, as RFC 9112 requires, and within 100..599. A code outside that range is not an extension we do not know about, it is a malformed response.
proc parseReason(buf: openArray): int64The reason phrase: spaces, tabs and printable bytes up to the line end. Looser than
parseUntilEolin allowing HTAB, which RFC 9112 permits here, and it is a response we are reading rather than one we are serving.proc parseStatusLine(buf: openArray; m: var HttpMsg): int64version SP status [SP reason] CRLF, opening the message node.The reason phrase is parsed and discarded. Nothing reads it — RFC 9110 tells clients to ignore it and lets a proxy replace it — and keeping it would mean a payload string on every response for no reader. The cost is that a response round trip is byte-identical only up to the phrase, which
httpwireregenerates canonically. Requests have no such gap.const MaxChunkSizeDigits: int64Hex digits allowed in a chunk size. Leading zeros are legal, so this cannot be tight enough to bound the value — the overflow check does that. It bounds the line, so a peer cannot send a megabyte of
0.const MaxChunkExtLen: int64Chunk extensions are parsed only to be skipped; nothing reads them.
const MaxTrailerCount: int64proc parseChunkSize(buf: openArray; size: var int64): int641*HEXDIG [ chunk-ext ] CRLF. Consumes up to the chunk's first data byte and setssize—0for the last chunk.Strict on purpose. A chunk size is the length of the next piece of the message, so a parser that accepts
+5,0x5or a value that wraps is a parser that can be made to disagree with the next hop about where this message ends. Only hex digits, and only a value that fits.proc parseTrailerEnd(buf: openArray): int64Skip the trailer section after the last chunk, and the blank line that ends it. Consumes everything up to and including that blank line.
Trailers are consumed, not kept: folding them into the head would let a peer set a header after the recipient has already acted on the ones it sent up front, which is the reason trailers are treated with suspicion.
proc framingIsUnambiguous(m: HttpMsg): boolWhether exactly one thing says where this message's body ends.
This is the request-smuggling check, and it is a rejection rather than a repair on purpose. If a message carries both
Content-LengthandTransfer-Encoding, or twoContent-Lengthlines, then two hops reading the same bytes can disagree about where it stops — and an attacker picks which hop believes which. RFC 9112 permits stripping theContent-Lengthinstead; doing that means trusting ourselves to strip it exactly as everything in front of us does, which is the assumption these attacks are built on.proc parseRequestHead(buf: openArray; m: var HttpMsg): int64A complete request head: the request line, its headers, and the blank line that ends them. Consumes up to the first body byte.
mmust be empty —initHttpMsgorreset. On a negative result it is left half-built and onlyresetis valid on it, which is fine because neither outcome lets the connection continue.proc parseResponseHead(buf: openArray; m: var HttpMsg): int64A complete response head: the status line, its headers, and the blank line. Consumes up to the first body byte — though whether there is a body at all depends on the status and on the request that provoked it, which is the connection layer's business, not this one's.
Same contract as
parseRequestHead:mmust be empty, and on a negative result it is left half-built and onlyresetis valid on it.type HeadScanner = object pos: int64
proc findHeadEnd(sc: var HeadScanner; buf: openArray): int64The length of the head including the blank line that ends it, or
ParseIncompleteif it has not arrived yet, orParseBadonce the head is overMaxHeadLen.Call it again after appending to
buf; it resumes where it stopped.