# Tier 1 Complete Reference: `cshttp.core` — The RFC 9112 Wire Engine > **Source Location:** [`src/cshttp/Core/`](../src/cshttp/Core/) > **Namespace:** `cshttp` > **Compilation:** Pure C# 7.3, zero external dependencies, compiles on `.NET Framework 4.8` and `.NET 8 / 10+`. --- ## 1. Overview & Architecture `cshttp.core` is the foundational tier of `cshttp`: a strict, resumable, byte-level **HTTP/1.1 wire parser, chunked transfer decoder, and response serializer** built directly against authoritative internet standards: * [RFC 9112 — HTTP/1.1](https://www.rfc-editor.org/rfc/rfc9112) * [RFC 9110 — HTTP Semantics](https://www.rfc-editor.org/rfc/rfc9110) * [RFC 3986 — Uniform Resource Identifier (URI)](https://www.rfc-editor.org/rfc/rfc3986) * [RFC 6265 — HTTP State Management Mechanism (Cookies)](https://www.rfc-editor.org/rfc/rfc6265) * [RFC 7578 — Returning Values from Forms: multipart/form-data](https://www.rfc-editor.org/rfc/rfc7578) ### Design Principles 1. **Zero Socket / Network I/O**: The engine is purely computational. It knows nothing about sockets, listeners, SSL, or files. You pass it incoming `byte[]` chunks; it validates, inspects, and yields structured message heads and bodies. 2. **Resumable & Allocation-Free on Incomplete Data**: Unlike naive parsers that call `StreamReader.ReadLine()` or allocate strings on partial TCP packets, `HttpRequestParser.ParseHead` scans strictly for delimiter bounds. Until the terminating double CRLF (`\r\n\r\n`) arrives, it allocates **zero objects on the managed heap** and returns `HttpParseStatus.NeedMoreData`. 3. **Zero Regular Expressions (ReDoS Immune)**: Untrusted client byte streams are validated with RFC character-class lookup tables (`HttpChars`), completely eliminating Regular Expression Denial of Service (ReDoS) vectors. 4. **Active Request-Smuggling Defense**: Enforces RFC 9112 §6.3, rejecting TE+CL conflicts, duplicate/differing `Content-Length` headers, bare CR injection, and obsolete line folding (`obs-fold`). --- ## 2. File Inventory (`src/cshttp/Core/` — 7 Source Files) | Source File | Public Types | Architectural Role | | :--- | :--- | :--- | | **`HttpRequestParser.cs`** | `HttpRequestParser` | Strict, resumable HTTP/1.x request-head parser (`ParseHead`) and RFC comma-list splitter (`SplitList`). | | **`HttpRequestHead.cs`** | `HttpRequestHead`
`HttpParseResult`
`HttpParseStatus`
`HttpBodyKind`
`RequestTargetForm` | Parsed request-line, headers, body framing resolution (`None`, `ContentLength`, `Chunked`), keep-alive decision, and `100-continue` flag. | | **`HttpHeaders.cs`** | `HttpHeaders`
`HttpHeader`
`KnownHeaders` | Wire-order-preserving header list with case-insensitive lookup and zero-allocation canonical name interning (`KnownHeaders.Find`). | | **`ChunkedDecoder.cs`** | `ChunkedDecoder`
`ChunkedStatus` | Incremental, zero-copy finite state machine for `Transfer-Encoding: chunked` (RFC 9112 §7.1). | | **`HttpResponseWriter.cs`** | `HttpResponseWriter`
`HttpStatus`
`HttpDate` | Serializes HTTP/1.1 response status lines, headers, chunk framing, and IMF-fixdate formatting with CRLF injection protection. | | **`HttpChars.cs`** | `HttpChars`
`ParserOptions` | RFC 9110/9112 byte character-class lookup tables (`IsToken`, `IsFieldValueByte`, `IsSafeFieldValue`) and parser safety limits. | | **`ByteScan.cs`** | `ByteScan` | SIMD vectorized scanning: uses `ReadOnlySpan.IndexOf` on modern `.NET 8+` and fast loops on `.NET Framework 4.8` without external packages. | --- ## 3. Resumable Request-Head Parsing (`HttpRequestParser.ParseHead`) The primary entry point for parsing HTTP wire traffic is `HttpRequestParser.ParseHead`: ```csharp public static HttpParseResult ParseHead( byte[] buffer, int offset, int count, ParserOptions options, out HttpRequestHead head) ``` ### Execution Flow 1. **Accumulate**: You pass the slice of your buffer (`buffer, offset, count`) representing bytes received from the network so far. 2. **Scan**: `ParseHead` verifies if the header terminator `\r\n\r\n` (or `\n\n` in lenient mode) exists. 3. **Evaluate**: * If not found: Returns `HttpParseStatus.NeedMoreData`. You wait for the socket to read more bytes and call `ParseHead` again with the updated count. * If found: Parses the start-line, decodes the request method and URI target, interns known headers, resolves body framing, and returns `HttpParseStatus.Complete`. * If malformed: Returns `HttpParseStatus.Error` with the exact HTTP error status code (`400`, `414`, `431`, `501`, `505`). ### Code Example: Socket Buffer Parsing ```csharp using System; using cshttp; var options = new ParserOptions { MaxRequestLineLength = 8 * 1024, // 414 URI Too Long if exceeded MaxHeaderBytes = 32 * 1024, // 431 Request Header Fields Too Large MaxHeaderCount = 100, // 431 if exceeded AllowBareLf = false // Strict RFC CRLF enforcement }; HttpRequestHead head; HttpParseResult result = HttpRequestParser.ParseHead(buf, start, end - start, options, out head); switch (result.Status) { case HttpParseStatus.NeedMoreData: // Keep buffer intact, read more bytes into 'buf' at 'end', call ParseHead again break; case HttpParseStatus.Error: // Reply with result.ErrorStatusCode (400, 414, 431, 505) and close socket Console.WriteLine($"[HTTP {result.ErrorStatusCode}] {result.ErrorMessage}"); break; case HttpParseStatus.Complete: // Advance buffer pointer past the head: start += result.Consumed; Console.WriteLine($"Method : {head.Method}"); // e.g. "POST" Console.WriteLine($"Path : {head.Path}"); // Raw path, e.g. "/api/upload" Console.WriteLine($"Query : {head.Query}"); // Raw query string or null Console.WriteLine($"Version : {head.Version}"); // "HTTP/1.1" Console.WriteLine($"Host : {head.Host}"); // Canonical Host header Console.WriteLine($"KeepAlive : {head.KeepAlive}"); // Resolved connection reuse Console.WriteLine($"BodyKind : {head.BodyKind}"); // None | ContentLength | Chunked Console.WriteLine($"ContentLength : {head.ContentLength}");// Length in bytes (-1 for chunked) break; } ``` --- ## 4. Message Models & Structures ### `HttpRequestHead` Represents the fully validated HTTP request envelope: | Property | Type | Description | | :--- | :--- | :--- | | `Method` | `string` | HTTP method verb (`"GET"`, `"POST"`, `"PUT"`, `"DELETE"`, etc.). Common methods are interned. | | `Target` | `string` | The exact request target as received on the wire (`"/index.html?ref=1"`). | | `TargetForm` | `RequestTargetForm` | RFC 9112 form: `Origin` (`/path`), `Absolute` (`http://...`), `Authority` (`host:port`), or `Asterisk` (`*`). | | `Path` | `string` | Extracted URI path component. | | `Query` | `string` | Extracted raw query string (without leading `?`), or `null` if absent. | | `Version` | `string` | HTTP version (`"HTTP/1.1"` or `"HTTP/1.0"`). | | `Headers` | `HttpHeaders` | The wire-ordered header collection. | | `Host` | `string` | Validated Host header authority. | | `KeepAlive` | `bool` | RFC 9112 connection lifetime resolution (`true` for keep-alive, `false` for close). | | `Expect100Continue`| `bool` | `true` if `Expect: 100-continue` header was sent by the client. | | `BodyKind` | `HttpBodyKind` | `None`, `ContentLength`, or `Chunked`. | | `ContentLength` | `long` | Declared body length in bytes (`0` for `None`, `-1` for `Chunked`). | ### `HttpHeaders` Collection `HttpHeaders` preserves strict wire order while enabling case-insensitive $\mathcal{O}(1)$ lookups: * **`headers["Host"]`**: Returns the value of the header, or `null` if not found. If multiple occurrences exist, returns a comma-delimited combination per RFC 9110 §5.3. * **`headers.GetValues("Header-Name")`**: Returns an array of all values for the specified name without comma-merging (mandatory for multiple `Set-Cookie` headers). * **`headers.Contains("Header-Name")`**: Fast boolean check. * **`headers.Add(name, value)`**: Appends a header while checking for forbidden characters. --- ## 5. Body Framing Rules (RFC 9112 §6.3) `cshttp.core` strictly implements the 8 precedence rules defined in **RFC 9112 Section 6.3**: | Precedence | Condition | Resulting `HttpBodyKind` | Action | | :---: | :--- | :---: | :--- | | **Rule 1** | 1xx, 204, or 304 response | `None` | Any `Content-Length` or `Transfer-Encoding` is ignored or rejected. | | **Rule 2** | 2xx response to `CONNECT` | `Tunnel` | Direct socket tunnel established. | | **Rule 3** | `Transfer-Encoding` + `Content-Length` | `Error (400)` | **Request Smuggling Guard**: Any message containing both framing headers is rejected immediately. | | **Rule 4** | `Transfer-Encoding: chunked` | `Chunked` | Body is framed using chunked transfer coding; final chunk `0\r\n\r\n` concludes the stream. | | **Rule 5** | Duplicate `Content-Length` | `ContentLength` or `Error (400)` | Multiple identical values (e.g. `10, 10`) are collapsed; conflicting values (e.g. `10, 20`) trigger `400 Bad Request`. | | **Rule 6** | Valid single `Content-Length` | `ContentLength` | Exactly `Content-Length` bytes form the message body. | | **Rule 7** | Request with neither TE nor CL | `None` | The request has no message body (`ContentLength = 0`). | | **Rule 8** | Response with neither TE nor CL | `UntilClose` | Response body continues until the server closes the TCP socket. | --- ## 6. Incremental Zero-Copy `ChunkedDecoder` When `head.BodyKind == HttpBodyKind.Chunked`, `ChunkedDecoder` parses chunk boundaries, hex sizes, chunk extensions, and trailers incrementally without intermediate byte copying: ```csharp var decoder = new ChunkedDecoder { MaxLineLength = 4096, MaxTrailerBytes = 16 * 1024, AllowBareLf = false }; while (true) { int consumed, dataOffset, dataCount; ChunkedStatus status = decoder.Decode(buf, start, end - start, out consumed, out dataOffset, out dataCount); start += consumed; if (status == ChunkedStatus.Data) { // Zero-copy slice directly inside the existing 'buf': outputStream.Write(buf, dataOffset, dataCount); continue; } if (status == ChunkedStatus.Complete) { // Terminal chunk '0\r\n\r\n' and all trailers have been fully parsed! // start now points to the next pipelined HTTP request in the buffer. break; } if (status == ChunkedStatus.NeedMoreData) { // Read more bytes from network into 'buf' break; } if (status == ChunkedStatus.Error) { throw new InvalidOperationException("Chunked decode error: " + decoder.ErrorMessage); } } ``` --- ## 7. Response Serialization (`HttpResponseWriter`) `HttpResponseWriter` constructs wire-ready byte arrays for outbound HTTP responses. It enforces strict header validation to protect against **HTTP Response Splitting**: ```csharp var headers = new HttpHeaders(); headers.Add("Date", HttpDate.Format(DateTime.UtcNow)); headers.Add("Content-Type", "text/html; charset=utf-8"); headers.Add("Content-Length", bodyBytes.Length.ToString()); headers.Add("Connection", "keep-alive"); // Serializes the status line and headers into a wire-ready ASCII/Latin-1 byte[] // Throws ArgumentException if any header key or value contains raw \r, \n, or \0 characters. byte[] headBytes = HttpResponseWriter.BuildHead(200, "OK", headers); ``` ### Chunked Response Helpers When streaming dynamic output where total length is unknown: * `HttpResponseWriter.WriteChunkHeader(long size)`: Returns the chunk size in hex followed by CRLF (`e.g. "1A4\r\n"`). * `HttpResponseWriter.FinalChunk`: Static byte array containing the terminal chunk (`"0\r\n\r\n"`). --- ## 8. Security Specifications & Defenses | Attack Vector | RFC Reference | `cshttp.core` Enforcement | Error Code | | :--- | :--- | :--- | :---: | | **TE.CL / CL.TE Smuggling** | RFC 9112 §6.3 | Messages with both `Transfer-Encoding` and `Content-Length` are rejected immediately. | `400 Bad Request` | | **Duplicate `Content-Length`** | RFC 9112 §6.3 | Conflicting `Content-Length` values are rejected. Identical duplicates are safely collapsed. | `400 Bad Request` | | **Whitespace Before Colon** | RFC 9112 §5.1 | `Header-Name : value` is rejected as an invalid smuggling probe. | `400 Bad Request` | | **Obsolete Line Folding (`obs-fold`)** | RFC 9112 §5.2 | Header continuations starting with SP/HTAB are rejected by default. | `400 Bad Request` | | **Bare CR / LF Injection** | RFC 9112 §2.2 | Bare `\r` is rejected everywhere. Bare `\n` is rejected unless `AllowBareLf = true`. | `400 Bad Request` | | **Header Injection / Response Splitting** | RFC 9110 | Header keys and values must pass `HttpChars.IsToken` and `HttpChars.IsSafeFieldValue`. | `ArgumentException` | | **URI & Header Flooding** | RFC 9112 | Strict bounds: `MaxRequestLineLength` (8 KB) and `MaxHeaderBytes` (32 KB) enforced before allocations. | `414` / `431` | | **ReDoS (Regex Denial of Service)** | — | **Zero regular expressions** used on untrusted byte input. | Immune | --- ## 9. Configuration (`ParserOptions`) ```csharp public sealed class ParserOptions { public int MaxRequestLineLength { get; set; } = 8192; // 8 KB public int MaxHeaderBytes { get; set; } = 32768; // 32 KB public int MaxHeaderCount { get; set; } = 100; // Max number of header lines public bool AllowBareLf { get; set; } = false; // false = Strict CRLF required public bool RejectObsFold { get; set; } = true; // Reject obsolete line folding } ``` --- ## 10. Specification & RFC Reference Links | Standard | Title & Link | Scope in `cshttp.core` | | :--- | :--- | :--- | | **RFC 9112** | [HTTP/1.1 (Message Syntax and Routing)](https://www.rfc-editor.org/rfc/rfc9112) | Start-line grammar, header field lines, chunked transfer coding, request smuggling defenses (TE+CL), and body framing rules (§6.3). | | **RFC 9110** | [HTTP Semantics](https://www.rfc-editor.org/rfc/rfc9110) | HTTP methods, status codes, reason phrases, character tokens (`HttpChars`), and IMF-fixdate format (`HttpDate`). | | **RFC 3986** | [Uniform Resource Identifier (URI): Generic Syntax](https://www.rfc-editor.org/rfc/rfc3986) | Request-target forms (Origin, Absolute, Authority, Asterisk), path and query string delimiters, percent-decoding rules. | | **RFC 6265** | [HTTP State Management Mechanism (Cookies)](https://www.rfc-editor.org/rfc/rfc6265) | `Cookie` and `Set-Cookie` header serialization and token constraints. | | **RFC 7578** | [Returning Values from Forms: multipart/form-data](https://www.rfc-editor.org/rfc/rfc7578) | Boundary syntax, multi-part headers, and form-data framing. | | **WHATWG URL** | [WHATWG URL Standard](https://url.spec.whatwg.org/) | Query parameter parsing, `application/x-www-form-urlencoded`, and `+` to space mapping. | --- ## 11. Summary: When to Use `cshttp.core` * **Use `cshttp.core` directly** if you are writing custom network infrastructure (TCP reverse proxies, TLS terminators, API gateways, network packet inspectors, or custom socket loops) and only require high-performance, specification-compliant HTTP/1.1 byte framing without web abstractions. * **For general web application development**, use the unified [`cshttp`](../README.md) micro web server, which bundles `cshttp.core`, the `cshttp.web` toolkit (`HttpRequest`, `HttpResponse`, `Session`, `Cookies`, `StaticFiles`), and the `HttpServer` socket listener into one complete stack.