Skip to content
Parsing Headers
step 1/5

Reading — step 1 of 5

Read

~3 min readConnection & Parsing

Parsing Headers

After the request line come the headers — the request's metadata, one Name: value per line, ended by one blank line. The format looks trivial, and the ways it isn't trivial are precisely the parts exercises (and attackers) test. This lesson builds a header parser with the three normalization rules every real server applies.

The shape on the wire

GET /api/users HTTP/1.1\r\n
Host: example.com\r\n
Content-Type: application/json\r\n
Content-Length: 27\r\n
\r\n                              ← blank line: headers end here

Per line: a field name, a colon, optional surrounding whitespace, the value, CRLF. The blank line (\r\n alone) is load-bearing — it's the only signal that headers are over and the body (if any) begins. A parser loops: read line; blank → done; otherwise split at the first colon.

Rule 1: names are case-insensitive; values are not

Content-Length, content-length, and CONTENT-LENGTH are the same header — the spec says so, and clients exercise the freedom. The standard implementation move is to normalize names to lowercase at parse time and store them that way, so every later lookup is one exact-match. Values keep their case untouched — Content-Type: TEXT/HTML normalized to lowercase would corrupt values where case matters (ETags, base64 credentials).

Rule 2: trim the value, split at the FIRST colon

Host: example.com → name host, value example.com — optional whitespace around the value is the sender's whim, trimmed by you. And first colon only:

Referer: http://example.com/page

Split at every colon and this shatters. Name is everything before the first colon; value is everything after it (trimmed). One more validation with security teeth: no whitespace allowed before the colonContent-Length : 5 is malformed (400), because parsers disagreeing about that space is an actual request-smuggling vector. Strictness at boundaries, again.

Rule 3: duplicates happen — have a policy

The same header can legally appear twice. The general rule: equivalent to one header with comma-joined values (Accept: a + Accept: bAccept: a, b). But Set-Cookie breaks the rule (cookies can contain commas — it must stay a list), and a duplicated Content-Length with different values is an attack signature (smuggling again) that real servers reject outright. Your exercise specs its policy — comma-join, keep-list, or reject — and the meta-lesson is that you need a policy: the parser that silently keeps "the last one" has made a security decision by accident.

The headers that run the show

Parsing is generic; a few names steer the whole server, worth knowing as you store them: Host (mandatory in HTTP/1.1 — its absence is a 400; virtual hosting hangs off it), Content-Length (how many body bytes follow — next lesson's whole subject), Connection (keep-alive vs close — chapter 3), Content-Type, and Transfer-Encoding (the Content-Length alternative with its own lesson). Your parsed, normalized header map is the input to every remaining chapter — build it boringly correct.

Your exercise: Parse and Normalize Headers

Raw header blocks in; normalized name→value map out (or 400 for malformed lines), per the spec. The ladder: basic pairs → case normalization → whitespace trimming → colon-in-value → duplicate handling per spec → space-before-colon rejection → missing blank line. Every rule above is one test and a few lines of code — and jointly they're the difference between "parses my test request" and "parses the internet."

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…