Tighten HTTP parsing

* HTTP version strictly digits
* Status code strictly 3-digit number
This commit is contained in:
dmiller 2026-07-31 20:57:25 +00:00
parent 18b3db3d67
commit 5001a59a87

View file

@ -503,8 +503,7 @@ static const char *skip_crlf(const char *s)
else if (*s == '\r' && *(s + 1) == '\n')
return s + 2;
ncat_assert(0);
return NULL;
return s;
}
static int field_name_equal(const char *a, const char *b)
@ -627,7 +626,7 @@ static const char *read_quoted_string(const char *s, char **quoted_string)
/* Get a block of normal characters. */
while (*t != '"' && *t != '\\') {
/* This is qdtext, which is TEXT except for CTL. */
if (is_ctl_char(*t)) {
if (is_ctl_char(*t) && !is_space_char(*t)) {
FREE_AND_NULL(free, buf);
return NULL;
}
@ -1082,9 +1081,11 @@ int http_read_request_line(struct socket_buffer *buf, char **line)
parse error. */
static const char *parse_http_version(const char *s, enum http_version *version)
{
/* HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT */
static const char PREFIX[] = "HTTP/";
const size_t prefixlen = sizeof(PREFIX) - 1;
const char *p, *q;
int dot = 0;
*version = HTTP_UNKNOWN;
@ -1093,12 +1094,16 @@ static const char *parse_http_version(const char *s, enum http_version *version)
return s;
p += sizeof(PREFIX) - 1;
q = strpbrk(p, " \t\r\n");
if (!q)
q = p + strlen(p);
/* Any version is accepted and not a parse error,
* but only 1.0 and 1.1 are understood. */
q = p;
while (*q && (isdigit(*q) || (*q == '.' && dot++ == 0))) {
q++;
}
if (*q != '\0' && *q != ' ' && !is_crlf(q)) {
return s;
}
if ((q - p) != 3)
return q;
if (0 == strncmp(p, "1.0", 3))
@ -1159,7 +1164,7 @@ int http_parse_request_line(const char *line, struct http_request *request)
p = q;
while (*p == ' ')
p++;
if (*p == '\0') {
if (*p == '\0' || is_crlf(p)) {
/* No HTTP/X.X version number indicates version 0.9. */
request->version = HTTP_09;
} else {
@ -1194,6 +1199,28 @@ int http_read_status_line(struct socket_buffer *buf, char **line)
return 0;
}
static int parse_code(const char *p, const char **tail) {
int code = 0;
#define CODE_DIGIT(_Place) do { \
if (!isdigit((int) (unsigned char) *p)) { \
*tail = p; \
return -1; \
} \
code += (*p - 0x30) * _Place; \
p++; \
} while (0)
CODE_DIGIT(100);
CODE_DIGIT(10);
CODE_DIGIT(1);
*tail = p;
if (*p != '\0' && !isspace((int) (unsigned char) *p)) {
return -1;
}
return code;
}
/* Returns 0 on success and nonzero on failure. */
int http_parse_status_line(const char *line, struct http_response *response)
{
@ -1210,8 +1237,8 @@ int http_parse_status_line(const char *line, struct http_response *response)
/* Status code. */
errno = 0;
response->code = parse_long(p, &q);
if (errno != 0 || q == p)
response->code = parse_code(p, &q);
if (response->code < 0)
return -1;
p = q;
@ -1219,8 +1246,12 @@ int http_parse_status_line(const char *line, struct http_response *response)
while (*p == ' ')
p++;
q = p;
while (!is_crlf(q))
while (*q != '\0' && !is_crlf(q)) {
if (is_ctl_char(*q) && !is_space_char(*q)) {
return -1;
}
q++;
}
/* We expect that the CRLF ends the string. */
if (*skip_crlf(q) != '\0')
return -1;