| 251 | } |
| 252 | |
| 253 | int cbm_http_parse_head(const char *data, size_t len, cbm_http_req_t *req, size_t *body_offset, |
| 254 | size_t *content_length) { |
| 255 | memset(req, 0, sizeof(*req)); |
| 256 | *body_offset = 0; |
| 257 | *content_length = 0; |
| 258 | |
| 259 | /* Scan for the CRLFCRLF terminator. While scanning, enforce the strict |
| 260 | * byte rules: no raw NUL, no LF that is not preceded by CR. */ |
| 261 | size_t scan = len < CBM_HTTP_MAX_HEAD ? len : CBM_HTTP_MAX_HEAD; |
| 262 | size_t head_end = 0; /* offset just past CRLFCRLF */ |
| 263 | for (size_t i = 0; i < scan; i++) { |
| 264 | char ch = data[i]; |
| 265 | if (ch == '\0') |
| 266 | return 400; |
| 267 | if (ch == '\n' && (i == 0 || data[i - 1] != '\r')) |
| 268 | return 400; |
| 269 | if (ch == '\n' && i >= 3 && data[i - 3] == '\r' && data[i - 2] == '\n') { |
| 270 | head_end = i + 1; |
| 271 | break; |
| 272 | } |
| 273 | } |
| 274 | if (head_end == 0) |
| 275 | return len >= CBM_HTTP_MAX_HEAD ? 431 : CBM_HTTP_NEED_MORE; |
| 276 | |
| 277 | /* ── Request line: METHOD SP request-target SP HTTP/1.x ── */ |
| 278 | const char *line = data; |
| 279 | const char *line_end = memchr(line, '\r', head_end); |
| 280 | if (!line_end) |
| 281 | return 400; |
| 282 | |
| 283 | const char *sp1 = memchr(line, ' ', (size_t)(line_end - line)); |
| 284 | if (!sp1) |
| 285 | return 400; |
| 286 | size_t mlen = (size_t)(sp1 - line); |
| 287 | if (mlen == 0 || mlen >= sizeof(req->method)) |
| 288 | return 400; |
| 289 | for (size_t i = 0; i < mlen; i++) { |
| 290 | if (!isupper((unsigned char)line[i])) |
| 291 | return 400; |
| 292 | } |
| 293 | memcpy(req->method, line, mlen); |
| 294 | req->method[mlen] = '\0'; |
| 295 | |
| 296 | const char *target = sp1 + 1; |
| 297 | const char *sp2 = memchr(target, ' ', (size_t)(line_end - target)); |
| 298 | if (!sp2) |
| 299 | return 400; |
| 300 | const char *version = sp2 + 1; |
| 301 | size_t vlen = (size_t)(line_end - version); |
| 302 | /* Exactly HTTP/1.0 or HTTP/1.1 */ |
| 303 | if (vlen != 8 || memcmp(version, "HTTP/1.", 7) != 0 || (version[7] != '0' && version[7] != '1')) |
| 304 | return 400; |
| 305 | |
| 306 | size_t tlen = (size_t)(sp2 - target); |
| 307 | if (tlen == 0 || target[0] != '/') |
| 308 | return 400; |
| 309 | /* Reject percent-encoded NUL anywhere in the request target. */ |
| 310 | for (size_t i = 0; i + 2 < tlen; i++) { |
no test coverage detected