| 455 | } |
| 456 | |
| 457 | int cbm_http_parse_head(const char *data, size_t len, cbm_http_req_t *req, size_t *body_offset, |
| 458 | size_t *content_length) { |
| 459 | memset(req, 0, sizeof(*req)); |
| 460 | *body_offset = 0; |
| 461 | *content_length = 0; |
| 462 | |
| 463 | /* Scan for the CRLFCRLF terminator. While scanning, enforce the strict |
| 464 | * byte rules: no raw NUL, no LF that is not preceded by CR. */ |
| 465 | size_t scan = len < CBM_HTTP_MAX_HEAD ? len : CBM_HTTP_MAX_HEAD; |
| 466 | size_t head_end = 0; /* offset just past CRLFCRLF */ |
| 467 | for (size_t i = 0; i < scan; i++) { |
| 468 | char ch = data[i]; |
| 469 | if (ch == '\0') |
| 470 | return 400; |
| 471 | if (ch == '\n' && (i == 0 || data[i - 1] != '\r')) |
| 472 | return 400; |
| 473 | if (ch == '\n' && i >= 3 && data[i - 3] == '\r' && data[i - 2] == '\n') { |
| 474 | head_end = i + 1; |
| 475 | break; |
| 476 | } |
| 477 | } |
| 478 | if (head_end == 0) |
| 479 | return len >= CBM_HTTP_MAX_HEAD ? 431 : CBM_HTTP_NEED_MORE; |
| 480 | |
| 481 | /* ── Request line: METHOD SP request-target SP HTTP/1.x ── */ |
| 482 | const char *line = data; |
| 483 | const char *line_end = memchr(line, '\r', head_end); |
| 484 | if (!line_end) |
| 485 | return 400; |
| 486 | |
| 487 | const char *sp1 = memchr(line, ' ', (size_t)(line_end - line)); |
| 488 | if (!sp1) |
| 489 | return 400; |
| 490 | size_t mlen = (size_t)(sp1 - line); |
| 491 | if (mlen == 0 || mlen >= sizeof(req->method)) |
| 492 | return 400; |
| 493 | for (size_t i = 0; i < mlen; i++) { |
| 494 | if (!isupper((unsigned char)line[i])) |
| 495 | return 400; |
| 496 | } |
| 497 | memcpy(req->method, line, mlen); |
| 498 | req->method[mlen] = '\0'; |
| 499 | |
| 500 | const char *target = sp1 + 1; |
| 501 | const char *sp2 = memchr(target, ' ', (size_t)(line_end - target)); |
| 502 | if (!sp2) |
| 503 | return 400; |
| 504 | const char *version = sp2 + 1; |
| 505 | size_t vlen = (size_t)(line_end - version); |
| 506 | /* Exactly HTTP/1.0 or HTTP/1.1 */ |
| 507 | if (vlen != 8 || memcmp(version, "HTTP/1.", 7) != 0 || (version[7] != '0' && version[7] != '1')) |
| 508 | return 400; |
| 509 | req->http_minor = (unsigned char)(version[7] - '0'); |
| 510 | |
| 511 | size_t tlen = (size_t)(sp2 - target); |
| 512 | if (tlen == 0 || target[0] != '/') |
| 513 | return 400; |
| 514 | /* Reject percent-encoded NUL anywhere in the request target. */ |
no test coverage detected