summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/request.c144
-rw-r--r--src/t/test_request.c26
-rwxr-xr-xtests/mod-fastcgi.t18
3 files changed, 102 insertions, 86 deletions
diff --git a/src/request.c b/src/request.c
index fc66ac9e..12932af5 100644
--- a/src/request.c
+++ b/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;
}
diff --git a/src/t/test_request.c b/src/t/test_request.c
index fa2593d6..712727fe 100644
--- a/src/t/test_request.c
+++ b/src/t/test_request.c
@@ -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) */
diff --git a/tests/mod-fastcgi.t b/tests/mod-fastcgi.t
index 7a00963c..4ae50d1f 100755
--- a/tests/mod-fastcgi.t
+++ b/tests/mod-fastcgi.t
@@ -7,7 +7,7 @@ BEGIN {
}
use strict;
-use Test::More tests => 46;
+use Test::More tests => 44;
use LightyTest;
my $tf = LightyTest->new();
@@ -194,22 +194,6 @@ EOF
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
EOF