[core] perf: request processing

personal/stbuehler/ci-build
Glenn Strauss 2019-06-09 23:55:39 -04:00
parent 2230b08ef4
commit 6f803af03c
1 changed files with 147 additions and 151 deletions

View File

@ -452,19 +452,9 @@ static void init_parse_header_state(parse_header_state* state) {
*
* returns 0 on success, HTTP status on error
*/
static int parse_single_header(server *srv, connection *con, parse_header_state *state, char *k, size_t klen, char *v, size_t vlen) {
const enum http_header_e id = http_header_hkey_get(k, klen);
static int parse_single_header(server *srv, connection *con, parse_header_state *state, const enum http_header_e id, char *k, size_t klen, char *v, size_t vlen) {
buffer **saveb = NULL;
/* strip leading whitespace */
for (; vlen > 0 && (v[0] == ' ' || v[0] == '\t'); ++v, --vlen) ;
/* strip trailing whitespace */
while (vlen > 0 && (v[vlen - 1] == ' ' || v[vlen - 1] == '\t')) --vlen;
/* empty header-fields are not allowed by HTTP-RFC, we just ignore them */
if (0 == vlen) return 0; /* ignore header */
/*
* Note: k might not be '\0'-terminated
*/
@ -554,7 +544,6 @@ static int parse_single_header(server *srv, connection *con, parse_header_state
break;
}
con->request.htags |= id;
http_header_request_append(con, id, k, klen, v, vlen);
if (saveb) {
@ -598,6 +587,10 @@ static size_t http_request_parse_reqline(server *srv, connection *con, buffer *h
ptr[i] = '\0';
state->reqline_len = i+1;
if (NULL == proto) {
return http_request_header_line_invalid(srv, 400, "incomplete request line -> 400");
}
{
char *nuri = NULL;
size_t j, jlen;
@ -610,10 +603,6 @@ static size_t http_request_parse_reqline(server *srv, connection *con, buffer *h
return http_request_header_line_invalid(srv, 400, "missing CR before LF in header -> 400");
}
if (NULL == proto) {
return http_request_header_line_invalid(srv, 400, "incomplete request line -> 400");
}
con->request.http_method = get_http_method_key(ptr, uri - 1 - ptr);
if (HTTP_METHOD_UNSET == con->request.http_method) {
return http_request_header_line_invalid(srv, 501, "unknown http-method -> 501");
@ -689,11 +678,147 @@ static size_t http_request_parse_reqline(server *srv, connection *con, buffer *h
return 0;
}
__attribute_noinline__
static int http_request_parse_header_other(server *srv, const char *k, int klen, const unsigned int http_header_strict) {
for (int i = 0; i < klen; ++i) {
if (light_isalpha(k[i]) || k[i] == '-') continue; /*(common cases)*/
/**
* 1*<any CHAR except CTLs or separators>
* CTLs == 0-31 + 127, CHAR = 7-bit ascii (0..127)
*
*/
switch(k[i]) {
case ' ':
case '\t':
return http_request_header_line_invalid(srv, 400, "WS character in key -> 400");
case '(':
case ')':
case '<':
case '>':
case '@':
case ',':
case ';':
case '\\':
case '\"':
case '/':
case '[':
case ']':
case '?':
case '=':
case '{':
case '}':
return http_request_header_char_invalid(srv, k[i], "invalid character in header key -> 400");
default:
if (http_header_strict ? (k[i] < 32 || ((unsigned char *)k)[i] >= 127) : k[i] == '\0')
return http_request_header_char_invalid(srv, k[i], "invalid character in header key -> 400");
break; /* ok */
}
}
return 0;
}
static int http_request_parse_headers(server *srv, connection *con, buffer *hdrs, parse_header_state *state) {
char * const ptr = hdrs->ptr;
const unsigned int http_header_strict = (con->conf.http_parseopts & HTTP_PARSEOPT_HEADER_STRICT);
size_t i = state->reqline_len;
if (ptr[i] == ' ' || ptr[i] == '\t') {
return http_request_header_line_invalid(srv, 400, "WS at the start of first line -> 400");
}
const size_t ilen = buffer_string_length(hdrs);
for (; i < ilen; ++i) {
char *k = ptr + i;
char *end = k;
while ((end = memchr(end, '\n', ilen - i)) && (end[1] == ' ' || end[1] == '\t')) {
/* line folding */
if (end != k && end[-1] == '\r')
end[-1] = ' ';
else if (http_header_strict)
return http_request_header_line_invalid(srv, 400, "missing CR before LF in header -> 400");
end[0] = ' ';
end += 2;
}
if (NULL == end) /*(should not happen)*/
return http_request_header_line_invalid(srv, 400, "missing LF in header -> 400");
i = end - ptr;
if (i+1 == ilen) break; /* end of headers */
if (end != k && end[-1] == '\r')
--end;
else if (http_header_strict)
return http_request_header_line_invalid(srv, 400, "missing CR before LF in header -> 400");
/* remove trailing whitespace from value */
while (end != k && (end[-1] == ' ' || end[-1] == '\t')) --end;
/*(for if value is further parsed (in parse_single_header())
* and '\0' is expected at end of string)*/
*end = '\0';
char *colon = memchr(k, ':', end - k);
if (NULL == colon)
return http_request_header_line_invalid(srv, 400, "invalid header missing ':' -> 400");
char *v = colon + 1;
/* RFC7230 Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
* 3.2.4. Field Parsing
* [...]
* No whitespace is allowed between the header field-name and colon. In
* the past, differences in the handling of such whitespace have led to
* security vulnerabilities in request routing and response handling. A
* server MUST reject any received request message that contains
* whitespace between a header field-name and colon with a response code
* of 400 (Bad Request). A proxy MUST remove any such whitespace from a
* response message before forwarding the message downstream.
*/
/* (line k[-1] is always preceded by a '\n',
* including first header after request-line,
* so no need to check colon != k) */
if (colon[-1] == ' ' || colon[-1] == '\t') {
if (http_header_strict) {
return http_request_header_line_invalid(srv, 400, "invalid whitespace between field-name and colon -> 400");
}
else {
/* remove trailing whitespace from key(if !http_header_strict)*/
do { --colon; } while (colon[-1] == ' ' || colon[-1] == '\t');
}
}
const int klen = (int)(colon - k);
const enum http_header_e id = http_header_hkey_get(k, klen);
if (id == HTTP_HEADER_OTHER
&& 0 != http_request_parse_header_other(srv, k, klen, http_header_strict)) {
return 400;
}
/* remove leading whitespace from value */
while (*v == ' ' || *v == '\t') ++v;
const int vlen = (int)(end - v);
/* empty header-fields are not allowed by HTTP-RFC, we just ignore them */
if (0 == vlen) continue; /* ignore header */
if (http_header_strict) {
for (int j = 0; j < vlen; ++j) {
if ((((unsigned char *)v)[j] < 32 && v[j] != '\t') || v[j]==127)
return http_request_header_char_invalid(srv, v[j], "invalid character in header -> 400");
}
}
else {
for (int j = 0; j < vlen; ++j) {
if (v[j] == '\0')
return http_request_header_char_invalid(srv, v[j], "invalid character in header -> 400");
}
}
int status = parse_single_header(srv, con, state, id, k, (size_t)klen, v, (size_t)vlen);
if (0 != status) return status;
}
return 0;
}
int http_request_parse(server *srv, connection *con, buffer *hdrs) {
char * const ptr = hdrs->ptr;
char *value = NULL;
size_t i, first, ilen;
const unsigned int http_header_strict = (con->conf.http_parseopts & HTTP_PARSEOPT_HEADER_STRICT);
int status;
parse_header_state state;
@ -702,137 +827,8 @@ int http_request_parse(server *srv, connection *con, buffer *hdrs) {
status = http_request_parse_reqline(srv, con, hdrs, &state);
if (0 != status) return status;
i = first = state.reqline_len;
if (ptr[i] == ' ' || ptr[i] == '\t') {
return http_request_header_line_invalid(srv, 400, "WS at the start of first line -> 400");
}
ilen = buffer_string_length(hdrs);
for (int is_key = 1, key_len = 0; i < ilen; ++i) {
char *cur = ptr + i;
if (is_key) {
/**
* 1*<any CHAR except CTLs or separators>
* CTLs == 0-31 + 127, CHAR = 7-bit ascii (0..127)
*
*/
switch(*cur) {
case ' ':
case '\t':
/* RFC7230 Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
* 3.2.4. Field Parsing
* [...]
* No whitespace is allowed between the header field-name and colon. In
* the past, differences in the handling of such whitespace have led to
* security vulnerabilities in request routing and response handling. A
* server MUST reject any received request message that contains
* whitespace between a header field-name and colon with a response code
* of 400 (Bad Request). A proxy MUST remove any such whitespace from a
* response message before forwarding the message downstream.
*/
if (http_header_strict)
return http_request_header_line_invalid(srv, 400, "invalid whitespace between field-name and colon -> 400");
/* skip every thing up to the : */
do { ++cur; } while (*cur == ' ' || *cur == '\t');
if (*cur != ':') {
return http_request_header_line_invalid(srv, 400, "WS character in key -> 400");
}
/* fall through */
case ':':
is_key = 0;
key_len = i - first;
value = cur + 1;
i = cur - ptr;
break;
case '(':
case ')':
case '<':
case '>':
case '@':
case ',':
case ';':
case '\\':
case '\"':
case '/':
case '[':
case ']':
case '?':
case '=':
case '{':
case '}':
return http_request_header_char_invalid(srv, *cur, "invalid character in header key -> 400");
case '\r':
if (ptr[i+1] == '\n' && i == first) {
/* End of Header */
++i;
} else {
return http_request_header_line_invalid(srv, 400, "CR without LF -> 400");
}
break;
case '\n':
if (http_header_strict) {
return http_request_header_line_invalid(srv, 400, "missing CR before LF in header -> 400");
} else if (i == first) {
/* End of Header */
break;
}
/* fall through */
default:
if (http_header_strict ? (*cur < 32 || ((unsigned char)*cur) >= 127) : *cur == '\0') {
return http_request_header_char_invalid(srv, *cur, "invalid character in header key -> 400");
}
/* ok */
break;
}
} else {
switch(*cur) {
case '\r':
if (cur[1] != '\n') {
return http_request_header_line_invalid(srv, 400, "CR without LF -> 400");
}
if (cur[2] == ' ' || cur[2] == '\t') { /* header line folding */
cur[0] = ' ';
cur[1] = ' ';
i += 2;
continue;
}
++i;
/* fall through */
case '\n':
if (*cur == '\n') {
if (http_header_strict) {
return http_request_header_line_invalid(srv, 400, "missing CR before LF in header -> 400");
}
if (cur[1] == ' ' || cur[1] == '\t') { /* header line folding */
cur[0] = ' ';
i += 1;
continue;
}
}
/* End of Headerline */
*cur = '\0'; /*(for if value is further parsed and '\0' is expected at end of string)*/
status = parse_single_header(srv, con, &state, ptr + first, key_len, value, cur - value);
if (0 != status) return status;
first = i+1;
is_key = 1;
value = NULL;
break;
case ' ':
case '\t':
break;
default:
if (http_header_strict ? (*cur >= 0 && *cur < 32) : *cur == '\0') {
return http_request_header_char_invalid(srv, *cur, "invalid character in header -> 400");
}
break;
}
}
}
status = http_request_parse_headers(srv, con, hdrs, &state);
if (0 != status) return status;
/* do some post-processing */