18#if DETWS_ENABLE_STREAM_BODY
24 HttpStreamBeginCb stream_begin =
nullptr;
25 HttpStreamDataCb stream_data =
nullptr;
26 HttpStreamAbortCb stream_abort =
nullptr;
28static HttpParserCtx s_hp;
30void http_parser_set_stream_hooks(HttpStreamBeginCb begin, HttpStreamDataCb data, HttpStreamAbortCb abort)
32 s_hp.stream_begin = begin;
33 s_hp.stream_data = data;
34 s_hp.stream_abort = abort;
46static constexpr uint32_t FNV_OFFSET = 2166136261u;
47static constexpr uint32_t FNV_PRIME = 16777619u;
49static constexpr uint32_t fnv1a(
const char *s, uint32_t h = FNV_OFFSET)
51 return *s ? fnv1a(s + 1, (h ^ (uint8_t)*s) * FNV_PRIME) : h;
54static constexpr uint32_t HASH_HTTP10 = fnv1a(
"HTTP/1.0");
55static constexpr uint32_t HASH_HTTP11 = fnv1a(
"HTTP/1.1");
69static constexpr uint8_t CC_TCHAR = 0x01;
70static constexpr uint8_t CC_VCHAR = 0x02;
71static constexpr uint8_t CC_FIELD_VALUE = 0x04;
77static const uint8_t kCharClass[256] = {
78 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
79 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x07, 0x06, 0x07, 0x07, 0x07,
80 0x07, 0x07, 0x06, 0x06, 0x07, 0x07, 0x06, 0x07, 0x07, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
81 0x07, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
82 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06, 0x07,
83 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
84 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x07, 0x06, 0x07, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04,
85 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
86 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
87 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
88 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
89 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
90 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
91 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
94static inline bool is_tchar(uint8_t b)
96 return (kCharClass[b] & CC_TCHAR) != 0;
98static inline bool is_vchar(uint8_t b)
100 return (kCharClass[b] & CC_VCHAR) != 0;
102static inline bool is_field_value_char(uint8_t b)
104 return (kCharClass[b] & CC_FIELD_VALUE) != 0;
114static void parse_query_params(
HttpReq *req)
116 const char *qs = req->
query;
132 if (c ==
'=' && !in_val)
138 qp->
key[key_idx++] = c;
140 qp->
val[val_idx++] = c;
151#if DETWS_ENABLE_STREAM_BODY
157 s_hp.stream_abort(req);
180 else if (!is_tchar(
byte))
204 else if (!is_vchar(
byte))
222 parse_query_params(p);
225 else if (!is_vchar(
byte))
288#if DETWS_CAPTURE_AUTH_HEADER
291 p->cur_is_auth = (strcasecmp(p->
cur_key,
"Authorization") == 0);
296 else if (!is_tchar(
byte))
332#if DETWS_CAPTURE_AUTH_HEADER
335 p->authorization[p->auth_idx] =
'\0';
336 p->cur_is_auth =
false;
343 if (strcasecmp(p->
cur_key,
"Host") == 0)
346 if (strcasecmp(p->
cur_key,
"Content-Length") == 0)
350 bool valid = (p->
cur_val[0] !=
'\0');
351 for (
const char *q = p->
cur_val; *q; q++)
353 if (*q <
'0' || *q >
'9')
358 cl = cl * 10 + (size_t)(*q -
'0');
377 if (strcasecmp(p->
cur_key,
"Transfer-Encoding") == 0)
389 else if (!is_field_value_char(
byte))
396#if DETWS_CAPTURE_AUTH_HEADER
399 p->authorization[p->auth_idx++] = c;
429#if DETWS_ENFORCE_HOST_HEADER
431 host_violation =
true;
435#if DETWS_ENABLE_STREAM_BODY
440 else if (p->
content_length > 0 && s_hp.stream_begin && s_hp.stream_begin(p))
442 p->body_streaming =
true;
466#if DETWS_ENABLE_STREAM_BODY
467 if (p->body_streaming)
475 if (s_hp.stream_data)
482 if (p->
body_len && s_hp.stream_data)
510 if (strcasecmp(req->
headers[i].
key, key) == 0)
518 if (out ==
nullptr || out_size == 0)
521 if (req ==
nullptr || name ==
nullptr || name[0] ==
'\0')
534 while (*p ==
' ' || *p ==
'\t' || *p ==
';')
539 while (*eq !=
'\0' && *eq !=
'=' && *eq !=
';')
541 if (*eq ==
'=' && (
size_t)(eq - p) == nlen && strncmp(p, name, nlen) == 0)
543 const char *v = eq + 1;
545 while (*end !=
'\0' && *end !=
';')
547 while (end > v && (end[-1] ==
' ' || end[-1] ==
'\t'))
549 size_t vlen = (size_t)(end - v);
550 if (vlen >= 2 && v[0] ==
'"' && v[vlen - 1] ==
'"')
555 if (vlen >= out_size)
557 memcpy(out, v, vlen);
562 while (*p !=
'\0' && *p !=
';')
574static bool fwd_extract_client(
const char *s,
size_t n,
char *out,
size_t cap)
577 while (n > 0 && (*s ==
' ' || *s ==
'\t'))
582 while (n > 0 && (s[n - 1] ==
' ' || s[n - 1] ==
'\t'))
584 if (n >= 2 && s[0] ==
'"' && s[n - 1] ==
'"')
598 for (; i < n && s[i] !=
']'; i++)
600 if (tlen + 1 >=
sizeof(tok))
612 for (
size_t i = 0; i < n; i++)
617 for (
size_t i = 0; i < n; i++)
623 if (take == 0 || take + 1 >
sizeof(tok))
625 memcpy(tok, s, take);
640 if (!ip_out || ip_cap == 0 || !req)
650 const char *elem_end = strchr(fwd,
',');
651 size_t elen = elem_end ? (size_t)(elem_end - fwd) : strnlen(fwd,
MAX_VAL_LEN);
656 const char *hit = strstr(fwd,
"proto=");
657 if (hit && (
size_t)(hit - fwd) < elen)
658 *is_https = (strncasecmp(hit + 6,
"https", 5) == 0);
661 const char *f = strstr(fwd,
"for=");
662 if (f && (
size_t)(f - fwd) < elen)
664 const char *fv = f + 4;
665 const char *fend = fv;
666 size_t lim = elen - (size_t)(fv - fwd);
668 while (k < lim && fend[k] !=
';' && fend[k] !=
',')
670 if (fwd_extract_client(fv, k, ip_out, ip_cap))
679 if (xfp && strncasecmp(xfp,
"https", 5) == 0)
685 const char *end = strchr(xff,
',');
686 size_t len = end ? (size_t)(end - xff) : strnlen(xff,
MAX_VAL_LEN);
687 if (fwd_extract_client(xff, len, ip_out, ip_cap))
705 if (out ==
nullptr || out_size == 0)
708 if (req ==
nullptr || key ==
nullptr)
713 if (ct ==
nullptr || strncasecmp(ct,
"application/x-www-form-urlencoded", 33) != 0)
716 const char *body = (
const char *)req->
body;
718 size_t key_len = strnlen(key, len + 1);
724 while (i < len && body[i] !=
'=' && body[i] !=
'&')
726 bool key_matches = (i - ks == key_len) && (strncmp(body + ks, key, key_len) == 0);
730 if (i < len && body[i] ==
'=')
733 while (i < len && body[i] !=
'&')
737 if (i < len && body[i] ==
'&')
742 size_t vlen = ve - vs;
743 if (vlen > out_size - 1)
745 memcpy(out, body + vs, vlen);
755 if (req ==
nullptr || key ==
nullptr)
#define MAX_VAL_LEN
Maximum header field-value length.
#define MAX_QUERY_PARAMS
Maximum number of parsed query-string parameters.
#define MAX_QUERY_LEN
Maximum raw query-string length (everything after ?).
#define QUERY_VAL_LEN
Maximum query-parameter value length.
#define BODY_BUF_SIZE
Maximum request body bytes stored in HttpReq::body.
#define MAX_HEADERS
Maximum HTTP headers stored per request.
#define MAX_PATH_LEN
Maximum URL path length (including leading /).
#define QUERY_KEY_LEN
Maximum query-parameter key length.
#define MAX_KEY_LEN
Maximum header field-name length (e.g. "Content-Type").
#define DETWS_AUTH_HDR_CAP
const char * http_get_param(const HttpReq *req, const char *key)
Look up a captured path parameter by name (case-sensitive).
void http_parser_reset(HttpReq *req)
Reset a parser context to the initial (ParseState::PARSE_METHOD) state.
const char * http_get_header(const HttpReq *req, const char *key)
Look up a header value by name (case-insensitive).
HttpReq http_pool[CONN_POOL_SLOTS]
Pool of parser contexts, one per connection-pool slot (incl. reserved dispatch slots).
bool http_get_cookie(const HttpReq *req, const char *name, char *out, size_t out_size)
Read a named cookie from the request Cookie header (RFC 6265 4.2.1).
void http_parser_feed(HttpReq *p, uint8_t byte)
Feed one byte to the parser state machine.
bool http_forwarded_client(const HttpReq *req, char *ip_out, size_t ip_cap, bool *is_https)
Recover the original client from a reverse-proxy Forwarded (RFC 7239) or de-facto X-Forwarded-For / X...
bool http_get_form(const HttpReq *req, const char *key, char *out, size_t out_size)
Look up an application/x-www-form-urlencoded body field by name.
const char * http_get_query(const HttpReq *req, const char *key)
Look up a query parameter value by name (case-sensitive).
Standalone HTTP/1.1 request parser - no transport dependency.
@ PARSE_QUERY
Reading the raw query string (after ?).
@ PARSE_METHOD
Reading the HTTP method (GET, POST, …).
@ PARSE_ERROR
Unrecoverable parse failure → 400.
@ PARSE_ENTITY_TOO_LARGE
Content-Length > BODY_BUF_SIZE → 413.
@ PARSE_BODY
Reading the request body.
@ PARSE_COMPLETE
Full request parsed; ready for dispatch.
@ PARSE_HEADER_VAL
Reading a header field value.
@ PARSE_VERSION
Accumulating HTTP/1.x - hashed for validation.
@ PARSE_EXPECT_BODY_LF
Consuming the LF of the blank-line CRLF.
@ PARSE_HEADER_KEY
Reading a header field name.
@ PARSE_PATH
Reading the URL path component.
@ PARSE_URI_TOO_LONG
Path exceeds MAX_PATH_LEN → 414.
@ PARSE_EXPECT_LF
Consuming the LF of a header-line CRLF pair.
@ HTTP_10
HTTP/1.0 - close semantics by default.
@ HTTP_11
HTTP/1.1 - persistent connection by default.
@ HTTP_UNKNOWN
Version string did not match any known token.
size_t det_ip_format(const DetIp *ip, char *out, size_t cap)
Format ip into out as its RFC 5952 canonical text.
bool det_ip_parse(const char *s, DetIp *out)
Parse an IPv4 or IPv6 textual address (RFC 4291 §2.2) into out.
Layer 3 (Network) - a family-tagged IP address (IPv4 or IPv6) with RFC-faithful text parsing,...
#define DET_IP_STR_MAX
Longest text an det_ip_format can produce, including the NUL (RFC 5952 v4-mapped).
A v4 or v6 address in network (big-endian) byte order.
Fully-parsed HTTP/1.1 request.
QueryParam query_params[MAX_QUERY_PARAMS]
Parsed key=value pairs.
Header headers[MAX_HEADERS]
Captured header fields.
char query[MAX_QUERY_LEN]
Raw query string (after ?).
char method[DETWS_METHOD_BUF_SIZE]
HTTP method, null-terminated (OPTIONS, or WebDAV methods when enabled).
uint32_t _version_hash
FNV-1a accumulator for version validation (internal).
uint8_t body[BODY_BUF_SIZE+1]
Stored body bytes, always null-terminated.
QueryParam path_params[MAX_PATH_PARAMS]
:name captures from the matched route.
uint8_t header_count
Valid entries in headers[].
size_t current_token_idx
Write cursor shared by key/value sub-states.
ParseState parse_state
Current parser state.
size_t path_idx
Write cursor into path[].
size_t content_length
Value of Content-Length header (0 if absent).
char cur_key[MAX_KEY_LEN]
Field-name of the in-progress header.
uint8_t path_param_count
Valid entries in path_params[].
uint8_t slot_id
Transport slot index (set by presentation layer).
uint8_t content_length_count
Number of Content-Length fields seen (RFC 7230 §3.3.2).
size_t body_len
Bytes stored in body[] (≤ BODY_BUF_SIZE).
HttpVersion version
Protocol version parsed from the request line.
uint8_t host_count
Number of Host fields seen (RFC 7230 §5.4).
char cur_val[MAX_VAL_LEN]
Field-value of the in-progress header.
char path[MAX_PATH_LEN]
URL path, null-terminated; no query string.
uint8_t query_count
Valid entries in query_params[].
size_t body_bytes_read
Body bytes received (may exceed BODY_BUF_SIZE).
size_t query_idx
Write cursor into query[].
A single parsed query-string parameter.
char val[QUERY_VAL_LEN]
Parameter value (empty string if absent).
char key[QUERY_KEY_LEN]
Parameter name, null-terminated.