[core] reduce use of struct parse_header_state

personal/stbuehler/ci-build
Glenn Strauss 2019-08-21 00:22:38 -04:00
parent cdf653f8ce
commit fa4ab19275
3 changed files with 102 additions and 86 deletions

View File

@ -374,23 +374,13 @@ static int http_request_header_char_invalid(connection *con, char ch, const char
return 400;
}
enum keep_alive_set {
HTTP_CONNECTION_UNSET,
HTTP_CONNECTION_KEEPALIVE,
HTTP_CONNECTION_CLOSE,
};
typedef struct {
enum keep_alive_set keep_alive_set;
char con_length_set;
char *reqline_host;
int reqline_hostlen;
size_t reqline_len;
} parse_header_state;
static void init_parse_header_state(parse_header_state* state) {
state->keep_alive_set = HTTP_CONNECTION_UNSET;
state->con_length_set = 0;
state->reqline_host = NULL;
state->reqline_hostlen = 0;
state->reqline_len = 0;
@ -402,7 +392,7 @@ static void init_parse_header_state(parse_header_state* state) {
*
* returns 0 on success, HTTP status on error
*/
static int parse_single_header(connection *con, parse_header_state *state, const enum http_header_e id, char *k, size_t klen, char *v, size_t vlen) {
static int http_request_parse_single_header(connection *con, const enum http_header_e id, const char *k, size_t klen, const char *v, size_t vlen) {
buffer **saveb = NULL;
/*
@ -420,8 +410,9 @@ static int parse_single_header(connection *con, parse_header_state *state, const
return http_request_header_line_invalid(con, 400, "uri-authority too long -> 400");
}
}
else if (state->reqline_host) {
/* ignore all Host: headers as we got Host in request line */
else if (NULL != con->request.http_host
&& buffer_is_equal_string(con->request.http_host, v, vlen)) {
/* ignore all Host: headers if match authority in request line */
return 0; /* ignore header */
}
else {
@ -432,11 +423,11 @@ static int parse_single_header(connection *con, parse_header_state *state, const
/* "Connection: close" is common case if header is present */
if ((vlen == 5 && buffer_eq_icase_ssn(v, CONST_STR_LEN("close")))
|| http_header_str_contains_token(v,vlen,CONST_STR_LEN("close"))) {
state->keep_alive_set = HTTP_CONNECTION_CLOSE;
con->keep_alive = 0;
break;
}
if (http_header_str_contains_token(v,vlen,CONST_STR_LEN("keep-alive"))){
state->keep_alive_set = HTTP_CONNECTION_KEEPALIVE;
con->keep_alive = 1;
break;
}
break;
@ -484,6 +475,21 @@ static int parse_single_header(connection *con, parse_header_state *state, const
}
}
break;
case HTTP_HEADER_TRANSFER_ENCODING:
if (HTTP_VERSION_1_0 == con->request.http_version) {
return http_request_header_line_invalid(con, 400, "HTTP/1.0 with Transfer-Encoding (bad HTTP/1.0 proxy?) -> 400");
}
if (!buffer_eq_icase_ss(v, vlen, CONST_STR_LEN("chunked"))) {
/* Transfer-Encoding might contain additional encodings,
* which are not currently supported by lighttpd */
return http_request_header_line_invalid(con, 501, NULL); /* Not Implemented */
}
con->request.content_length = -1;
/* Transfer-Encoding is a hop-by-hop header,
* which must not be blindly forwarded to backends */
return 0; /* skip header */
}
http_header_request_append(con, id, k, klen, v, vlen);
@ -558,6 +564,8 @@ static size_t http_request_parse_reqline(connection *con, buffer *hdrs, parse_he
if (proto[0]=='H' && proto[1]=='T' && proto[2]=='T' && proto[3]=='P' && proto[4] == '/') {
if (proto[5] == '1' && proto[6] == '.' && (proto[7] == '1' || proto[7] == '0')) {
con->request.http_version = (proto[7] == '1') ? HTTP_VERSION_1_1 : HTTP_VERSION_1_0;
/* keep-alive default: HTTP/1.1 -> true; HTTP/1.0 -> false */
con->keep_alive = (HTTP_VERSION_1_0 != con->request.http_version);
} else {
return http_request_header_line_invalid(con, 505, "unknown HTTP version -> 505");
}
@ -615,6 +623,8 @@ static size_t http_request_parse_reqline(connection *con, buffer *hdrs, parse_he
}
http_header_request_set(con, HTTP_HEADER_HOST, CONST_STR_LEN("Host"), state->reqline_host, state->reqline_hostlen);
con->request.http_host = http_header_request_get(con, HTTP_HEADER_HOST, CONST_STR_LEN("Host"));
if (buffer_string_is_empty(con->request.http_host))
return http_request_header_line_invalid(con, 400, "empty uri-authority in request-line -> 400");
}
return 0;
@ -753,7 +763,7 @@ static int http_request_parse_headers(connection *con, buffer *hdrs, parse_heade
}
}
int status = parse_single_header(con, state, id, k, (size_t)klen, v, (size_t)vlen);
int status = http_request_parse_single_header(con, id, k, (size_t)klen, v, (size_t)vlen);
if (0 != status) return status;
}
@ -774,37 +784,16 @@ int http_request_parse(connection *con, buffer *hdrs) {
/* do some post-processing */
if (con->request.http_version == HTTP_VERSION_1_1) {
if (state.keep_alive_set != HTTP_CONNECTION_CLOSE) {
/* no Connection-Header sent */
/* HTTP/1.1 -> keep-alive default TRUE */
con->keep_alive = 1;
} else {
con->keep_alive = 0;
}
/* RFC 2616, 14.23 */
if (con->request.http_host == NULL ||
buffer_string_is_empty(con->request.http_host)) {
return http_request_header_line_invalid(con, 400, "HTTP/1.1 but Host missing -> 400");
}
} else {
if (state.keep_alive_set == HTTP_CONNECTION_KEEPALIVE) {
/* no Connection-Header sent */
/* HTTP/1.0 -> keep-alive default FALSE */
con->keep_alive = 1;
} else {
con->keep_alive = 0;
}
}
/* check hostname field if it is set */
if (!buffer_is_empty(con->request.http_host) &&
0 != http_request_host_policy(con, con->request.http_host, con->proto)) {
return http_request_header_line_invalid(con, 400, "Invalid Hostname -> 400");
}
/* check hostname field if it is set */
if (con->request.http_host) {
if (0 != http_request_host_policy(con, con->request.http_host, con->proto))
return http_request_header_line_invalid(con, 400, "Invalid Hostname -> 400");
}
else {
/* RFC 2616, 14.23 */
if (con->request.http_version == HTTP_VERSION_1_1)
return http_request_header_line_invalid(con, 400, "HTTP/1.1 but Host missing -> 400");
}
if (con->request.htags & HTTP_HEADER_TRANSFER_ENCODING) {
buffer *vb = http_header_request_get(con, HTTP_HEADER_TRANSFER_ENCODING, CONST_STR_LEN("Transfer-Encoding"));
@ -844,32 +833,49 @@ int http_request_parse(connection *con, buffer *hdrs) {
}
}
state.con_length_set = 1;
con->request.content_length = -1;
}
}
else if (con->request.htags & HTTP_HEADER_CONTENT_LENGTH) {
state.con_length_set = 1;
}
switch(con->request.http_method) {
case HTTP_METHOD_GET:
case HTTP_METHOD_HEAD:
/* content-length is forbidden for those */
if (state.con_length_set && 0 != con->request.content_length
&& !(con->conf.http_parseopts & HTTP_PARSEOPT_METHOD_GET_BODY)) {
return http_request_header_line_invalid(con, 400, "GET/HEAD with content-length -> 400");
}
break;
case HTTP_METHOD_POST:
/* content-length is required for them */
if (!state.con_length_set) {
return http_request_header_line_invalid(con, 411, "POST-request, but content-length missing -> 411");
}
break;
default:
break;
}
if (0 == con->request.content_length) {
/* POST requires Content-Length (or Transfer-Encoding)
* (-1 == con->request.content_length when Transfer-Encoding: chunked)*/
if (HTTP_METHOD_POST == con->request.http_method
&& !(con->request.htags & HTTP_HEADER_CONTENT_LENGTH)) {
return http_request_header_line_invalid(con, 411, "POST-request, but content-length missing -> 411");
}
}
else {
/* (-1 == con->request.content_length when Transfer-Encoding: chunked)*/
if (-1 == con->request.content_length
&& (con->request.htags & HTTP_HEADER_CONTENT_LENGTH)) {
/* RFC7230 Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
* 3.3.3. Message Body Length
* [...]
* If a message is received with both a Transfer-Encoding and a
* Content-Length header field, the Transfer-Encoding overrides the
* Content-Length. Such a message might indicate an attempt to
* perform request smuggling (Section 9.5) or response splitting
* (Section 9.4) and ought to be handled as an error. A sender MUST
* remove the received Content-Length field prior to forwarding such
* a message downstream.
*/
const unsigned int http_header_strict =
(con->conf.http_parseopts & HTTP_PARSEOPT_HEADER_STRICT);
if (http_header_strict) {
return http_request_header_line_invalid(con, 400, "invalid Transfer-Encoding + Content-Length -> 400");
}
else {
/* ignore Content-Length */
http_header_request_unset(con, HTTP_HEADER_CONTENT_LENGTH, CONST_STR_LEN("Content-Length"));
}
}
if ((HTTP_METHOD_GET == con->request.http_method
|| HTTP_METHOD_HEAD == con->request.http_method)
&& !(con->conf.http_parseopts & HTTP_PARSEOPT_METHOD_GET_BODY)) {
return http_request_header_line_invalid(con, 400, "GET/HEAD with content-length -> 400");
}
}
return 0;
}

View File

@ -435,6 +435,32 @@ static void test_request_http_request_parse(connection *con)
"If-Modified-Since: \0\r\n"
"\r\n"));
run_http_request_parse(con, __LINE__, 0,
"absolute-uri in request-line (without Host)",
CONST_STR_LEN("GET http://zzz.example.org/ HTTP/1.1\r\n"
"Connection: close\r\n"
"\r\n"));
ds = (data_string *)
array_get_element_klen(con->request.headers, CONST_STR_LEN("Host"));
assert(ds && buffer_is_equal_string(ds->value, CONST_STR_LEN("zzz.example.org")));
run_http_request_parse(con, __LINE__, 0,
"absolute-uri in request-line (with Host match)",
CONST_STR_LEN("GET http://zzz.example.org/ HTTP/1.1\r\n"
"Host: zzz.example.org\r\n"
"Connection: close\r\n"
"\r\n"));
ds = (data_string *)
array_get_element_klen(con->request.headers, CONST_STR_LEN("Host"));
assert(ds && buffer_is_equal_string(ds->value, CONST_STR_LEN("zzz.example.org")));
run_http_request_parse(con, __LINE__, 400,
"absolute-uri in request-line (with Host mismatch)",
CONST_STR_LEN("GET http://zzz.example.org/ HTTP/1.1\r\n"
"Host: aaa.example.org\r\n"
"Connection: close\r\n"
"\r\n"));
/* (quick check that none of above tests were left in a state
* which resulted in subsequent tests returning 400 for other
* reasons) */

View File

@ -7,7 +7,7 @@ BEGIN {
}
use strict;
use Test::More tests => 46;
use Test::More tests => 44;
use LightyTest;
my $tf = LightyTest->new();
@ -193,22 +193,6 @@ EOF
$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'Basic' } ];
ok($tf->handle_http($t) == 0, '$_SERVER["AUTH_TYPE"]');
$t->{REQUEST} = ( <<EOF
GET /get-server-env.php?env=SERVER_NAME HTTP/1.0
Host: zzz.example.org
EOF
);
$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'zzz.example.org' } ];
ok($tf->handle_http($t) == 0, 'FastCGI + Host');
$t->{REQUEST} = ( <<EOF
GET http://zzz.example.org/get-server-env.php?env=SERVER_NAME HTTP/1.0
Host: aaa.example.org
EOF
);
$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'zzz.example.org' } ];
ok($tf->handle_http($t) == 0, 'SERVER_NAME (absolute url in request line)');
$t->{REQUEST} = ( <<EOF
GET /indexfile/ HTTP/1.0
Host: www.example.org