[core] reduce use of struct parse_header_state
parent
cdf653f8ce
commit
fa4ab19275
144
src/request.c
144
src/request.c
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) */
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue