Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_http_req.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
27#include "nilorea/n_http_req.h"
28
29#include <string.h>
30
31static int lc(int c) {
32 return (c >= 'A' && c <= 'Z') ? c - 'A' + 'a' : c;
33}
34
35/* Case-insensitive compare of an \p n-byte field name against a lowercase,
36 * NUL-terminated literal. */
37static int name_eq(const char* s, size_t n, const char* lit) {
38 size_t i;
39 for (i = 0; i < n; i++)
40 if (lit[i] == '\0' || lc((unsigned char)s[i]) != lit[i])
41 return 0;
42 return lit[n] == '\0';
43}
44
45/* A field-name must be a non-empty token: no controls, DEL, or whitespace (so
46 * "Content-Length " with a trailing space is rejected, not silently ignored,
47 * a request-smuggling vector against a downstream proxy). */
48static int name_token_ok(const char* s, size_t n) {
49 size_t i;
50 if (n == 0)
51 return 0;
52 for (i = 0; i < n; i++)
53 if ((unsigned char)s[i] <= 0x20 || (unsigned char)s[i] == 0x7f)
54 return 0;
55 return 1;
56}
57
58/* Parse a bounded non-negative decimal [s, s+n) into *out. Returns 1 on a clean
59 * parse within N_HTTP_REQ_BODY_MAX, else 0 (empty / non-digit / over cap).
60 */
61static int parse_body_len(const char* s, size_t n, long long* out) {
62 unsigned long long v = 0;
63 size_t i;
64 if (n == 0)
65 return 0;
66 for (i = 0; i < n; i++) {
67 if (s[i] < '0' || s[i] > '9')
68 return 0;
69 v = v * 10u + (unsigned)(s[i] - '0');
70 if (v > (unsigned long long)N_HTTP_REQ_BODY_MAX)
71 return 0;
72 }
73 *out = (long long)v;
74 return 1;
75}
76
77/* A valid byte in an origin-form request target: printable ASCII, no space, no
78 * '#' (a fragment must never reach the server). */
79static int target_ok(unsigned char c) {
80 return c > 0x20 && c < 0x7f && c != '#';
81}
82
83n_http_req_status n_http_request_parse(const char* buf, size_t len, n_http_request* out) {
84 size_t scan, i, sl, p, tstart, tend, line_start, nheaders = 0;
85 int have_clen = 0;
86
87 if (!buf || !out)
88 return N_HTTP_REQ_ERROR;
89 memset(out, 0, sizeof(*out));
90
91 /* locate the end of the head (CRLFCRLF) within the cap */
92 scan = len < N_HTTP_REQ_HEAD_MAX ? len : (size_t)N_HTTP_REQ_HEAD_MAX;
93 /* nothing shorter than the CRLFCRLF terminator itself can hold a head: the
94 * loop below cannot run, head_len stays 0, and since scan < 4 implies
95 * len < N_HTTP_REQ_HEAD_MAX the head_len test reports INCOMPLETE.
96 * The older cppcheck used by CI folds the ternary assignment above down to
97 * a single value of 0 and then calls this walk dead code, a valueflow
98 * false positive: scan follows the caller's len, unknown here. Spelling
99 * the bound as an explicit "if (scan < 4)" guard only moves the same
100 * diagnostic onto that guard, hence the inline suppression. */
101 // cppcheck-suppress knownConditionTrueFalse ; scan is caller-controlled, the walk is reachable
102 for (i = 0; i + 3 < scan; i++)
103 if (buf[i] == '\r' && buf[i + 1] == '\n' && buf[i + 2] == '\r' &&
104 buf[i + 3] == '\n') {
105 out->head_len = i + 4;
106 break;
107 }
108 if (out->head_len == 0)
111
112 /* ---- request line: METHOD SP target SP HTTP/1.x CRLF ---- */
113 sl = 0;
114 while (sl + 1 < out->head_len && !(buf[sl] == '\r' && buf[sl + 1] == '\n'))
115 sl++;
116 if (sl >= 4 && buf[0] == 'G' && buf[1] == 'E' && buf[2] == 'T' &&
117 buf[3] == ' ') {
118 out->method = N_HTTP_REQ_GET;
119 p = 4;
120 } else if (sl >= 5 && buf[0] == 'P' && buf[1] == 'O' && buf[2] == 'S' &&
121 buf[3] == 'T' && buf[4] == ' ') {
122 out->method = N_HTTP_REQ_POST;
123 p = 5;
124 } else {
125 return N_HTTP_REQ_ERROR;
126 }
127 /* origin-form target, from p to the next space */
128 tstart = p;
129 while (p < sl && buf[p] != ' ') {
130 if (!target_ok((unsigned char)buf[p]))
131 return N_HTTP_REQ_ERROR;
132 p++;
133 }
134 tend = p;
135 if (tend == tstart || buf[tstart] != '/' ||
136 tend - tstart > N_HTTP_REQ_PATH_MAX)
137 return N_HTTP_REQ_ERROR;
138 /* exactly one SP, then the version filling the rest of the line */
139 if (p >= sl || buf[p] != ' ')
140 return N_HTTP_REQ_ERROR;
141 p++;
142 if (sl - p != 8 || memcmp(buf + p, "HTTP/1.", 7) != 0 ||
143 (buf[p + 7] != '0' && buf[p + 7] != '1'))
144 return N_HTTP_REQ_ERROR;
145 memcpy(out->path, buf + tstart, tend - tstart);
146 out->path[tend - tstart] = '\0';
147
148 /* ---- header lines up to the blank line ---- */
149 line_start = sl + 2;
150 while (line_start + 1 < out->head_len) {
151 size_t le = line_start, colon, vs, vlen, nlen;
152 const char *name, *val;
153 while (le + 1 < out->head_len &&
154 !(buf[le] == '\r' && buf[le + 1] == '\n'))
155 le++;
156 if (le == line_start)
157 break; /* blank line: end of headers */
158 if (++nheaders > N_HTTP_REQ_HEADERS_MAX)
159 return N_HTTP_REQ_ERROR;
160 colon = line_start;
161 while (colon < le && buf[colon] != ':')
162 colon++;
163 if (colon >= le)
164 return N_HTTP_REQ_ERROR; /* header without a colon */
165 name = buf + line_start;
166 nlen = colon - line_start;
167 if (!name_token_ok(name, nlen))
168 return N_HTTP_REQ_ERROR;
169 vs = colon + 1;
170 while (vs < le && (buf[vs] == ' ' || buf[vs] == '\t'))
171 vs++;
172 val = buf + vs;
173 vlen = le - vs;
174 while (vlen > 0 && (val[vlen - 1] == ' ' || val[vlen - 1] == '\t'))
175 vlen--;
176 if (name_eq(name, nlen, "transfer-encoding"))
177 return N_HTTP_REQ_ERROR; /* no chunked, smuggling defense */
178 if (name_eq(name, nlen, "content-length")) {
179 long long cl;
180 if (have_clen) /* duplicate Content-Length, smuggling */
181 return N_HTTP_REQ_ERROR;
182 if (!parse_body_len(val, vlen, &cl))
183 return N_HTTP_REQ_ERROR;
184 out->content_length = cl;
185 have_clen = 1;
186 } else if (name_eq(name, nlen, "authorization")) {
187 /* capture the credential; refuse rather than truncate an over-long
188 * one (a truncated bearer token must never be treated as valid) */
189 if (out->authorization[0] != '\0' || vlen > N_HTTP_REQ_AUTH_MAX)
190 return N_HTTP_REQ_ERROR;
191 memcpy(out->authorization, val, vlen);
192 out->authorization[vlen] = '\0';
193 }
194 line_start = le + 2;
195 }
196 return N_HTTP_REQ_OK;
197}
static int parse_body_len(const char *s, size_t n, long long *out)
Definition n_http_req.c:61
n_http_req_status n_http_request_parse(const char *buf, size_t len, n_http_request *out)
parse an HTTP/1.1 request head from [buf, buf+len)
Definition n_http_req.c:83
static int target_ok(unsigned char c)
Definition n_http_req.c:79
static int name_eq(const char *s, size_t n, const char *lit)
Definition n_http_req.c:37
static int lc(int c)
Definition n_http_req.c:31
static int name_token_ok(const char *s, size_t n)
Definition n_http_req.c:48
Pure, strict, bounded parser for an HTTP/1.1 request head.
#define N_HTTP_REQ_HEAD_MAX
Largest request head (request line + headers, through CRLFCRLF) accepted.
Definition n_http_req.h:46
n_http_req_status
Result of n_http_request_parse.
Definition n_http_req.h:78
@ N_HTTP_REQ_INCOMPLETE
no CRLFCRLF yet, still under the cap
Definition n_http_req.h:80
@ N_HTTP_REQ_ERROR
malformed / oversized / unsupported
Definition n_http_req.h:81
@ N_HTTP_REQ_OK
a complete, valid head was parsed
Definition n_http_req.h:79
size_t head_len
Definition n_http_req.h:72
char authorization[256u+1]
Definition n_http_req.h:73
char path[2048u+1]
Definition n_http_req.h:70
#define N_HTTP_REQ_AUTH_MAX
Longest captured Authorization header value.
Definition n_http_req.h:54
long long content_length
Definition n_http_req.h:71
n_http_req_method method
Definition n_http_req.h:69
#define N_HTTP_REQ_BODY_MAX
Maximum declared Content-Length (body size), in bytes.
Definition n_http_req.h:52
@ N_HTTP_REQ_POST
Definition n_http_req.h:59
@ N_HTTP_REQ_GET
Definition n_http_req.h:58
#define N_HTTP_REQ_HEADERS_MAX
Maximum number of header lines.
Definition n_http_req.h:50
#define N_HTTP_REQ_PATH_MAX
Longest origin-form request target ('/'-prefixed, query included).
Definition n_http_req.h:48
A parsed request head.
Definition n_http_req.h:68
static uint64_t le(const unsigned char *p, int n)
read a little endian unsigned value
Definition n_pth.c:233