+++ /dev/null
-From: =?UTF-8?Q?Stefan_B=c3=bchler?= <stbuehler@web.de>
-To: lighttpd-announce@lists.lighttpd.net
-Message-ID: <8cbf0c87-1037-8f70-aa24-b2f14c95170d@web.de>
-Date: Sun, 26 Aug 2018 19:06:30 +0200
-Subject: use-after-free bug in header folding
-
-Hi,
-
-Or Peles reported some use-after-free scenarios related to header
-folding (continuing a header value on a whitespace indented line).
-
-The fix required some preparation and therefor consists of a series of 4
-patches, see git:
-
-https://git.lighttpd.net/lighttpd/lighttpd1.4.git/log/?qt=range&q=a9e131..df8e4f&showmsg=1
-
-There are currently no plans for a new release.
-
-cheers,
-Stefan
-
-diff --git a/src/request.c b/src/request.c
-index 213a87e1..e94d8591 100644
---- a/src/request.c
-+++ b/src/request.c
-@@ -408,26 +408,179 @@ static int request_uri_is_valid_char(unsigned char c) {
- return 1;
- }
-
--static int http_request_missing_CR_before_LF(server *srv, connection *con) {
-+static void http_request_missing_CR_before_LF(server *srv, connection *con) {
- if (srv->srvconf.log_request_header_on_error) {
- log_error_write(srv, __FILE__, __LINE__, "s", "missing CR before LF in header -> 400");
- log_error_write(srv, __FILE__, __LINE__, "Sb", "request-header:\n", con->request.request);
- }
-+}
-
-- con->http_status = 400;
-- con->keep_alive = 0;
-- con->response.keep_alive = 0;
-+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;
-+} 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;
-+}
-+
-+/* add a header to the list of headers; certain headers are also parsed in this state.
-+ *
-+ * Also might drop a header if deemed unnecessary/broken.
-+ *
-+ * returns 0 on error
-+ */
-+static int parse_single_header(server *srv, connection *con, parse_header_state *state, data_string *ds) {
-+ int cmp = 0;
-+
-+ /* empty header-fields are not allowed by HTTP-RFC, we just ignore them */
-+ if (buffer_string_is_empty(ds->value)) {
-+ goto drop_header;
-+ }
-+
-+ /* retreive values
-+ *
-+ *
-+ * the list of options is sorted to simplify the search
-+ */
-+
-+ if (0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Connection")))) {
-+ array *vals;
-+ size_t vi;
-+
-+ /* split on , */
-+
-+ vals = srv->split_vals;
-+
-+ array_reset(vals);
-+
-+ http_request_split_value(vals, ds->value);
-+
-+ for (vi = 0; vi < vals->used; vi++) {
-+ data_string *dsv = (data_string *)vals->data[vi];
-+
-+ if (0 == buffer_caseless_compare(CONST_BUF_LEN(dsv->value), CONST_STR_LEN("keep-alive"))) {
-+ state->keep_alive_set = HTTP_CONNECTION_KEEPALIVE;
-+
-+ break;
-+ } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(dsv->value), CONST_STR_LEN("close"))) {
-+ state->keep_alive_set = HTTP_CONNECTION_CLOSE;
-+
-+ break;
-+ }
-+ }
-+
-+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Content-Length")))) {
-+ char *err;
-+ off_t r;
-+
-+ if (state->con_length_set) {
-+ if (srv->srvconf.log_request_header_on_error) {
-+ log_error_write(srv, __FILE__, __LINE__, "s",
-+ "duplicate Content-Length-header -> 400");
-+ log_error_write(srv, __FILE__, __LINE__, "Sb",
-+ "request-header:\n",
-+ con->request.request);
-+ }
-+ goto invalid_header;
-+ }
-+
-+ r = strtoll(ds->value->ptr, &err, 10);
-+
-+ if (*err == '\0' && r >= 0) {
-+ state->con_length_set = 1;
-+ con->request.content_length = r;
-+ } else {
-+ log_error_write(srv, __FILE__, __LINE__, "sbs",
-+ "content-length broken:", ds->value, "-> 400");
-+ goto invalid_header;
-+ }
-+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Content-Type")))) {
-+ /* if dup, only the first one will survive */
-+ if (!con->request.http_content_type) {
-+ con->request.http_content_type = ds->value->ptr;
-+ } else {
-+ if (srv->srvconf.log_request_header_on_error) {
-+ log_error_write(srv, __FILE__, __LINE__, "s",
-+ "duplicate Content-Type-header -> 400");
-+ log_error_write(srv, __FILE__, __LINE__, "Sb",
-+ "request-header:\n",
-+ con->request.request);
-+ }
-+ goto invalid_header;
-+ }
-+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Host")))) {
-+ if (state->reqline_host) {
-+ /* ignore all host: headers as we got the host in the request line */
-+ goto drop_header;
-+ } else if (!con->request.http_host) {
-+ con->request.http_host = ds->value;
-+ } else {
-+ if (srv->srvconf.log_request_header_on_error) {
-+ log_error_write(srv, __FILE__, __LINE__, "s",
-+ "duplicate Host-header -> 400");
-+ log_error_write(srv, __FILE__, __LINE__, "Sb",
-+ "request-header:\n",
-+ con->request.request);
-+ }
-+ goto invalid_header;
-+ }
-+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-Modified-Since")))) {
-+ /* Proxies sometimes send dup headers
-+ * if they are the same we ignore the second
-+ * if not, we raise an error */
-+ if (!con->request.http_if_modified_since) {
-+ con->request.http_if_modified_since = ds->value->ptr;
-+ } else if (0 == strcasecmp(con->request.http_if_modified_since, ds->value->ptr)) {
-+ /* ignore it if they are the same */
-+ goto drop_header;
-+ } else {
-+ if (srv->srvconf.log_request_header_on_error) {
-+ log_error_write(srv, __FILE__, __LINE__, "s",
-+ "duplicate If-Modified-Since header -> 400");
-+ log_error_write(srv, __FILE__, __LINE__, "Sb",
-+ "request-header:\n",
-+ con->request.request);
-+ }
-+ goto invalid_header;
-+ }
-+ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-None-Match")))) {
-+ /* if dup, only the first one will survive */
-+ if (!con->request.http_if_none_match) {
-+ con->request.http_if_none_match = ds->value->ptr;
-+ } else {
-+ goto drop_header;
-+ }
-+ }
-+
-+ array_insert_unique(con->request.headers, (data_unset *)ds);
-+ return 1;
-+
-+drop_header:
-+ ds->free((data_unset *)ds);
-+ return 1;
-+
-+invalid_header:
-+ ds->free((data_unset *)ds);
- return 0;
- }
-
- int http_request_parse(server *srv, connection *con) {
-- char *uri = NULL, *proto = NULL, *method = NULL, con_length_set;
-+ char *uri = NULL, *proto = NULL, *method = NULL;
- int is_key = 1, key_len = 0, is_ws_after_key = 0, in_folding;
- char *value = NULL, *key = NULL;
-- char *reqline_host = NULL;
-- int reqline_hostlen = 0;
--
-- enum { HTTP_CONNECTION_UNSET, HTTP_CONNECTION_KEEPALIVE, HTTP_CONNECTION_CLOSE } keep_alive_set = HTTP_CONNECTION_UNSET;
-+ data_string *current_header = NULL;
-
- int line = 0;
-
-@@ -437,6 +590,9 @@ int http_request_parse(server *srv, connection *con) {
- int done = 0;
- const unsigned int http_header_strict = (con->conf.http_parseopts & HTTP_PARSEOPT_HEADER_STRICT);
-
-+ parse_header_state state;
-+ init_parse_header_state(&state);
-+
- /*
- * Request: "^(GET|POST|HEAD) ([^ ]+(\\?[^ ]+|)) (HTTP/1\\.[01])$"
- * Option : "^([-a-zA-Z]+): (.+)$"
-@@ -457,9 +613,7 @@ int http_request_parse(server *srv, connection *con) {
-
- #ifdef __COVERITY__
- if (buffer_string_length(con->request.request) < 2) {
-- con->keep_alive = 0;
-- con->http_status = 400;
-- return 0;
-+ goto failure;
- }
- #endif
- /* coverity[overflow_sink : FALSE] */
-@@ -467,12 +621,13 @@ int http_request_parse(server *srv, connection *con) {
- } else if (con->request_count > 0 &&
- con->request.request->ptr[1] == '\n') {
- /* we are in keep-alive and might get \n after a previous POST request.*/
-- if (http_header_strict) return http_request_missing_CR_before_LF(srv, con);
-+ if (http_header_strict) {
-+ http_request_missing_CR_before_LF(srv, con);
-+ goto failure;
-+ }
- #ifdef __COVERITY__
- if (buffer_string_length(con->request.request) < 1) {
-- con->keep_alive = 0;
-- con->http_status = 400;
-- return 0;
-+ goto failure;
- }
- #endif
- /* coverity[overflow_sink : FALSE] */
-@@ -482,9 +637,6 @@ int http_request_parse(server *srv, connection *con) {
- buffer_copy_buffer(con->parse_request, con->request.request);
- }
-
-- keep_alive_set = 0;
-- con_length_set = 0;
--
- /* parse the first line of the request
- *
- * should be:
-@@ -510,22 +662,19 @@ int http_request_parse(server *srv, connection *con) {
- con->parse_request->ptr[i] = '\0';
- ++i;
- } else if (http_header_strict) { /* '\n' */
-- return http_request_missing_CR_before_LF(srv, con);
-+ http_request_missing_CR_before_LF(srv, con);
-+ goto failure;
- }
- con->parse_request->ptr[i] = '\0';
-
- if (request_line_stage != 2) {
-- con->http_status = 400;
-- con->response.keep_alive = 0;
-- con->keep_alive = 0;
--
- if (srv->srvconf.log_request_header_on_error) {
- log_error_write(srv, __FILE__, __LINE__, "s", "incomplete request line -> 400");
- log_error_write(srv, __FILE__, __LINE__, "Sb",
- "request-header:\n",
- con->request.request);
- }
-- return 0;
-+ goto failure;
- }
-
- proto = con->parse_request->ptr + first;
-@@ -536,8 +685,6 @@ int http_request_parse(server *srv, connection *con) {
- /* we got the first one :) */
- if (HTTP_METHOD_UNSET == (r = get_http_method_key(method))) {
- con->http_status = 501;
-- con->response.keep_alive = 0;
-- con->keep_alive = 0;
-
- if (srv->srvconf.log_request_header_on_error) {
- log_error_write(srv, __FILE__, __LINE__, "s", "unknown http-method -> 501");
-@@ -546,7 +693,7 @@ int http_request_parse(server *srv, connection *con) {
- con->request.request);
- }
-
-- return 0;
-+ goto failure;
- }
-
- con->request.http_method = r;
-@@ -582,16 +729,13 @@ int http_request_parse(server *srv, connection *con) {
- }
-
- if (invalid_version) {
-- con->http_status = 400;
-- con->keep_alive = 0;
--
- if (srv->srvconf.log_request_header_on_error) {
- log_error_write(srv, __FILE__, __LINE__, "s", "unknown protocol -> 400");
- log_error_write(srv, __FILE__, __LINE__, "Sb",
- "request-header:\n",
- con->request.request);
- }
-- return 0;
-+ goto failure;
- }
-
- if (major_num == 1 && minor_num == 1) {
-@@ -607,19 +751,16 @@ int http_request_parse(server *srv, connection *con) {
- "request-header:\n",
- con->request.request);
- }
-- return 0;
-+ goto failure;
- }
- } else {
-- con->http_status = 400;
-- con->keep_alive = 0;
--
- if (srv->srvconf.log_request_header_on_error) {
- log_error_write(srv, __FILE__, __LINE__, "s", "unknown protocol -> 400");
- log_error_write(srv, __FILE__, __LINE__, "Sb",
- "request-header:\n",
- con->request.request);
- }
-- return 0;
-+ goto failure;
- }
-
- if (*uri == '/') {
-@@ -627,14 +768,14 @@ int http_request_parse(server *srv, connection *con) {
- buffer_copy_string_len(con->request.uri, uri, proto - uri - 1);
- } else if (0 == strncasecmp(uri, "http://", 7) &&
- NULL != (nuri = strchr(uri + 7, '/'))) {
-- reqline_host = uri + 7;
-- reqline_hostlen = nuri - reqline_host;
-+ state.reqline_host = uri + 7;
-+ state.reqline_hostlen = nuri - state.reqline_host;
-
- buffer_copy_string_len(con->request.uri, nuri, proto - nuri - 1);
- } else if (0 == strncasecmp(uri, "https://", 8) &&
- NULL != (nuri = strchr(uri + 8, '/'))) {
-- reqline_host = uri + 8;
-- reqline_hostlen = nuri - reqline_host;
-+ state.reqline_host = uri + 8;
-+ state.reqline_hostlen = nuri - state.reqline_host;
-
- buffer_copy_string_len(con->request.uri, nuri, proto - nuri - 1);
- } else if (!http_header_strict
-@@ -643,10 +784,8 @@ int http_request_parse(server *srv, connection *con) {
- /* everything looks good so far */
- buffer_copy_string_len(con->request.uri, uri, proto - uri - 1);
- } else {
-- con->http_status = 400;
-- con->keep_alive = 0;
- log_error_write(srv, __FILE__, __LINE__, "ss", "request-URI parse error -> 400 for:", uri);
-- return 0;
-+ goto failure;
- }
-
- /* check uri for invalid characters */
-@@ -660,33 +799,30 @@ int http_request_parse(server *srv, connection *con) {
- j = (NULL == z) ? jlen : (size_t)(z - con->request.uri->ptr);
- }
- if (j < jlen) {
-- con->http_status = 400;
-- con->keep_alive = 0;
--
-- if (srv->srvconf.log_request_header_on_error) {
-- unsigned char buf[2];
-- buf[0] = con->request.uri->ptr[j];
-- buf[1] = '\0';
--
-- if (con->request.uri->ptr[j] > 32 &&
-- con->request.uri->ptr[j] != 127) {
-- /* the character is printable -> print it */
-- log_error_write(srv, __FILE__, __LINE__, "ss",
-- "invalid character in URI -> 400",
-- buf);
-- } else {
-- /* a control-character, print ascii-code */
-- log_error_write(srv, __FILE__, __LINE__, "sd",
-- "invalid character in URI -> 400",
-- con->request.uri->ptr[j]);
-- }
--
-- log_error_write(srv, __FILE__, __LINE__, "Sb",
-- "request-header:\n",
-- con->request.request);
-+ if (srv->srvconf.log_request_header_on_error) {
-+ unsigned char buf[2];
-+ buf[0] = con->request.uri->ptr[j];
-+ buf[1] = '\0';
-+
-+ if (con->request.uri->ptr[j] > 32 &&
-+ con->request.uri->ptr[j] != 127) {
-+ /* the character is printable -> print it */
-+ log_error_write(srv, __FILE__, __LINE__, "ss",
-+ "invalid character in URI -> 400",
-+ buf);
-+ } else {
-+ /* a control-character, print ascii-code */
-+ log_error_write(srv, __FILE__, __LINE__, "sd",
-+ "invalid character in URI -> 400",
-+ con->request.uri->ptr[j]);
- }
-
-- return 0;
-+ log_error_write(srv, __FILE__, __LINE__, "Sb",
-+ "request-header:\n",
-+ con->request.request);
-+ }
-+
-+ goto failure;
- }
-
- buffer_copy_buffer(con->request.orig_uri, con->request.uri);
-@@ -711,17 +847,13 @@ int http_request_parse(server *srv, connection *con) {
- break;
- default:
- /* ERROR, one space to much */
-- con->http_status = 400;
-- con->response.keep_alive = 0;
-- con->keep_alive = 0;
--
- if (srv->srvconf.log_request_header_on_error) {
- log_error_write(srv, __FILE__, __LINE__, "s", "overlong request line -> 400");
- log_error_write(srv, __FILE__, __LINE__, "Sb",
- "request-header:\n",
- con->request.request);
- }
-- return 0;
-+ goto failure;
- }
-
- request_line_stage++;
-@@ -732,20 +864,16 @@ int http_request_parse(server *srv, connection *con) {
- in_folding = 0;
-
- if (buffer_string_is_empty(con->request.uri)) {
-- con->http_status = 400;
-- con->response.keep_alive = 0;
-- con->keep_alive = 0;
--
- if (srv->srvconf.log_request_header_on_error) {
- log_error_write(srv, __FILE__, __LINE__, "s", "no uri specified -> 400");
- log_error_write(srv, __FILE__, __LINE__, "Sb",
- "request-header:\n",
- con->request.request);
- }
-- return 0;
-+ goto failure;
- }
-
-- if (reqline_host) {
-+ if (state.reqline_host) {
- /* Insert as host header */
- data_string *ds;
-
-@@ -754,7 +882,7 @@ int http_request_parse(server *srv, connection *con) {
- }
-
- buffer_copy_string_len(ds->key, CONST_STR_LEN("Host"));
-- buffer_copy_string_len(ds->value, reqline_host, reqline_hostlen);
-+ buffer_copy_string_len(ds->value, state.reqline_host, state.reqline_hostlen);
- array_insert_unique(con->request.headers, (data_unset *)ds);
- con->request.http_host = ds->value;
- }
-@@ -799,10 +927,6 @@ int http_request_parse(server *srv, connection *con) {
- case '=':
- case '{':
- case '}':
-- con->http_status = 400;
-- con->keep_alive = 0;
-- con->response.keep_alive = 0;
--
- if (srv->srvconf.log_request_header_on_error) {
- log_error_write(srv, __FILE__, __LINE__, "sbsds",
- "invalid character in key", con->request.request, cur, *cur, "-> 400");
-@@ -811,7 +935,7 @@ int http_request_parse(server *srv, connection *con) {
- "request-header:\n",
- con->request.request);
- }
-- return 0;
-+ goto failure;
- case ' ':
- case '\t':
- if (i == first) {
-@@ -850,11 +974,7 @@ int http_request_parse(server *srv, connection *con) {
- con->request.request);
- }
-
-- con->http_status = 400;
-- con->response.keep_alive = 0;
-- con->keep_alive = 0;
--
-- return 0;
-+ goto failure;
- }
- }
-
-@@ -876,15 +996,13 @@ int http_request_parse(server *srv, connection *con) {
- con->request.request);
- }
-
-- con->http_status = 400;
-- con->keep_alive = 0;
-- con->response.keep_alive = 0;
-- return 0;
-+ goto failure;
- }
- break;
- case '\n':
- if (http_header_strict) {
-- return http_request_missing_CR_before_LF(srv, con);
-+ http_request_missing_CR_before_LF(srv, con);
-+ goto failure;
- } else if (i == first) {
- con->parse_request->ptr[i] = '\0';
- done = 1;
-@@ -893,10 +1011,6 @@ int http_request_parse(server *srv, connection *con) {
- /* fall through */
- default:
- if (http_header_strict ? (*cur < 32 || ((unsigned char)*cur) >= 127) : *cur == '\0') {
-- con->http_status = 400;
-- con->keep_alive = 0;
-- con->response.keep_alive = 0;
--
- if (srv->srvconf.log_request_header_on_error) {
- log_error_write(srv, __FILE__, __LINE__, "sbsds",
- "invalid character in key", con->request.request, cur, *cur, "-> 400");
-@@ -906,7 +1020,7 @@ int http_request_parse(server *srv, connection *con) {
- con->request.request);
- }
-
-- return 0;
-+ goto failure;
- }
- /* ok */
- break;
-@@ -916,9 +1030,13 @@ int http_request_parse(server *srv, connection *con) {
- case '\r':
- case '\n':
- if (*cur == '\n' || con->parse_request->ptr[i+1] == '\n') {
-- data_string *ds = NULL;
-+ int value_len;
-+
- if (*cur == '\n') {
-- if (http_header_strict) return http_request_missing_CR_before_LF(srv, con);
-+ if (http_header_strict) {
-+ http_request_missing_CR_before_LF(srv, con);
-+ goto failure;
-+ }
- } else { /* (con->parse_request->ptr[i+1] == '\n') */
- con->parse_request->ptr[i] = '\0';
- ++i;
-@@ -927,17 +1045,15 @@ int http_request_parse(server *srv, connection *con) {
- /* End of Headerline */
- con->parse_request->ptr[i] = '\0';
-
-+ value_len = cur - value;
-+
-+ /* strip trailing white-spaces */
-+ while (value_len > 0 && (value[value_len - 1] == ' ' || value[value_len - 1] == '\t')) {
-+ --value_len;
-+ }
-+
- if (in_folding) {
-- /**
-- * we use a evil hack to handle the line-folding
-- *
-- * As array_insert_unique() deletes 'ds' in the case of a duplicate
-- * ds points somewhere and we get a evil crash. As a solution we keep the old
-- * "key" and get the current value from the hash and append us
-- *
-- * */
--
-- if (!key || !key_len) {
-+ if (!current_header) {
- /* 400 */
-
- if (srv->srvconf.log_request_header_on_error) {
-@@ -948,193 +1064,35 @@ int http_request_parse(server *srv, connection *con) {
- con->request.request);
- }
-
--
-- con->http_status = 400;
-- con->keep_alive = 0;
-- con->response.keep_alive = 0;
-- return 0;
-+ goto failure;
- }
-
-- if (NULL != (ds = (data_string *)array_get_element_klen(con->request.headers, key, key_len))) {
-- buffer_append_string(ds->value, value);
-- }
-+ buffer_append_string_len(current_header->value, value, value_len);
- } else {
-- int s_len;
-- key = con->parse_request->ptr + first;
--
-- s_len = cur - value;
--
-- /* strip trailing white-spaces */
-- for (; s_len > 0 &&
-- (value[s_len - 1] == ' ' ||
-- value[s_len - 1] == '\t'); s_len--);
--
-- value[s_len] = '\0';
--
-- if (s_len > 0) {
-- int cmp = 0;
-- if (NULL == (ds = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) {
-- ds = data_string_init();
-- }
-- buffer_copy_string_len(ds->key, key, key_len);
-- buffer_copy_string_len(ds->value, value, s_len);
--
-- /* retreive values
-- *
-- *
-- * the list of options is sorted to simplify the search
-- */
--
-- if (0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Connection")))) {
-- array *vals;
-- size_t vi;
--
-- /* split on , */
--
-- vals = srv->split_vals;
--
-- array_reset(vals);
--
-- http_request_split_value(vals, ds->value);
--
-- for (vi = 0; vi < vals->used; vi++) {
-- data_string *dsv = (data_string *)vals->data[vi];
--
-- if (0 == buffer_caseless_compare(CONST_BUF_LEN(dsv->value), CONST_STR_LEN("keep-alive"))) {
-- keep_alive_set = HTTP_CONNECTION_KEEPALIVE;
--
-- break;
-- } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(dsv->value), CONST_STR_LEN("close"))) {
-- keep_alive_set = HTTP_CONNECTION_CLOSE;
--
-- break;
-- }
-- }
--
-- } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Content-Length")))) {
-- char *err;
-- off_t r;
--
-- if (con_length_set) {
-- con->http_status = 400;
-- con->keep_alive = 0;
--
-- if (srv->srvconf.log_request_header_on_error) {
-- log_error_write(srv, __FILE__, __LINE__, "s",
-- "duplicate Content-Length-header -> 400");
-- log_error_write(srv, __FILE__, __LINE__, "Sb",
-- "request-header:\n",
-- con->request.request);
-- }
-- array_insert_unique(con->request.headers, (data_unset *)ds);
-- return 0;
-- }
--
-- r = strtoll(ds->value->ptr, &err, 10);
--
-- if (*err == '\0' && r >= 0) {
-- con_length_set = 1;
-- con->request.content_length = r;
-- } else {
-- log_error_write(srv, __FILE__, __LINE__, "sbs",
-- "content-length broken:", ds->value, "-> 400");
--
-- con->http_status = 400;
-- con->keep_alive = 0;
--
-- array_insert_unique(con->request.headers, (data_unset *)ds);
-- return 0;
-- }
-- } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Content-Type")))) {
-- /* if dup, only the first one will survive */
-- if (!con->request.http_content_type) {
-- con->request.http_content_type = ds->value->ptr;
-- } else {
-- con->http_status = 400;
-- con->keep_alive = 0;
--
-- if (srv->srvconf.log_request_header_on_error) {
-- log_error_write(srv, __FILE__, __LINE__, "s",
-- "duplicate Content-Type-header -> 400");
-- log_error_write(srv, __FILE__, __LINE__, "Sb",
-- "request-header:\n",
-- con->request.request);
-- }
-- array_insert_unique(con->request.headers, (data_unset *)ds);
-- return 0;
-- }
-- } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Host")))) {
-- if (reqline_host) {
-- /* ignore all host: headers as we got the host in the request line */
-- ds->free((data_unset*) ds);
-- ds = NULL;
-- } else if (!con->request.http_host) {
-- con->request.http_host = ds->value;
-- } else {
-- con->http_status = 400;
-- con->keep_alive = 0;
--
-- if (srv->srvconf.log_request_header_on_error) {
-- log_error_write(srv, __FILE__, __LINE__, "s",
-- "duplicate Host-header -> 400");
-- log_error_write(srv, __FILE__, __LINE__, "Sb",
-- "request-header:\n",
-- con->request.request);
-- }
-- array_insert_unique(con->request.headers, (data_unset *)ds);
-- return 0;
-- }
-- } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-Modified-Since")))) {
-- /* Proxies sometimes send dup headers
-- * if they are the same we ignore the second
-- * if not, we raise an error */
-- if (!con->request.http_if_modified_since) {
-- con->request.http_if_modified_since = ds->value->ptr;
-- } else if (0 == strcasecmp(con->request.http_if_modified_since,
-- ds->value->ptr)) {
-- /* ignore it if they are the same */
--
-- ds->free((data_unset *)ds);
-- ds = NULL;
-- } else {
-- con->http_status = 400;
-- con->keep_alive = 0;
--
-- if (srv->srvconf.log_request_header_on_error) {
-- log_error_write(srv, __FILE__, __LINE__, "s",
-- "duplicate If-Modified-Since header -> 400");
-- log_error_write(srv, __FILE__, __LINE__, "Sb",
-- "request-header:\n",
-- con->request.request);
-- }
-- array_insert_unique(con->request.headers, (data_unset *)ds);
-- return 0;
-- }
-- } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-None-Match")))) {
-- /* if dup, only the first one will survive */
-- if (!con->request.http_if_none_match) {
-- con->request.http_if_none_match = ds->value->ptr;
-- } else {
-- ds->free((data_unset*) ds);
-- ds = NULL;
-- }
-+ /* process previous header */
-+ if (current_header) {
-+ data_string *ds = current_header;
-+ current_header = NULL;
-+ if (!parse_single_header(srv, con, &state, ds)) {
-+ /* parse_single_header should already have logged it */
-+ goto failure;
- }
-+ }
-
-- if (ds) array_insert_unique(con->request.headers, (data_unset *)ds);
-- } else {
-- /* empty header-fields are not allowed by HTTP-RFC, we just ignore them */
-+ key = con->parse_request->ptr + first;
-+
-+ if (NULL == (current_header = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) {
-+ current_header = data_string_init();
- }
-+
-+ buffer_copy_string_len(current_header->key, key, key_len);
-+ buffer_copy_string_len(current_header->value, value, value_len);
- }
-
- first = i+1;
- is_key = 1;
- value = NULL;
--#if 0
-- /**
-- * for Bug 1230 keep the key_len a live
-- */
- key_len = 0;
--#endif
- in_folding = 0;
- } else {
- if (srv->srvconf.log_request_header_on_error) {
-@@ -1142,10 +1100,7 @@ int http_request_parse(server *srv, connection *con) {
- "CR without LF", con->request.request, "-> 400");
- }
-
-- con->http_status = 400;
-- con->keep_alive = 0;
-- con->response.keep_alive = 0;
-- return 0;
-+ goto failure;
- }
- break;
- case ' ':
-@@ -1160,22 +1115,29 @@ int http_request_parse(server *srv, connection *con) {
- "invalid char in header", (int)*cur, "-> 400");
- }
-
-- con->http_status = 400;
-- con->keep_alive = 0;
--
-- return 0;
-+ goto failure;
- }
- break;
- }
- }
- }
-
-+ /* process last header */
-+ if (current_header) {
-+ data_string* ds = current_header;
-+ current_header = NULL;
-+ if (!parse_single_header(srv, con, &state, ds)) {
-+ /* parse_single_header should already have logged it */
-+ goto failure;
-+ }
-+ }
-+
- con->header_len = i;
-
- /* do some post-processing */
-
- if (con->request.http_version == HTTP_VERSION_1_1) {
-- if (keep_alive_set != HTTP_CONNECTION_CLOSE) {
-+ if (state.keep_alive_set != HTTP_CONNECTION_CLOSE) {
- /* no Connection-Header sent */
-
- /* HTTP/1.1 -> keep-alive default TRUE */
-@@ -1187,9 +1149,6 @@ int http_request_parse(server *srv, connection *con) {
- /* RFC 2616, 14.23 */
- if (con->request.http_host == NULL ||
- buffer_string_is_empty(con->request.http_host)) {
-- con->http_status = 400;
-- con->response.keep_alive = 0;
-- con->keep_alive = 0;
-
- if (srv->srvconf.log_request_header_on_error) {
- log_error_write(srv, __FILE__, __LINE__, "s", "HTTP/1.1 but Host missing -> 400");
-@@ -1197,10 +1156,10 @@ int http_request_parse(server *srv, connection *con) {
- "request-header:\n",
- con->request.request);
- }
-- return 0;
-+ goto failure;
- }
- } else {
-- if (keep_alive_set == HTTP_CONNECTION_KEEPALIVE) {
-+ if (state.keep_alive_set == HTTP_CONNECTION_KEEPALIVE) {
- /* no Connection-Header sent */
-
- /* HTTP/1.0 -> keep-alive default FALSE */
-@@ -1222,11 +1181,7 @@ int http_request_parse(server *srv, connection *con) {
- con->request.request);
- }
-
-- con->http_status = 400;
-- con->response.keep_alive = 0;
-- con->keep_alive = 0;
--
-- return 0;
-+ goto failure;
- }
-
- {
-@@ -1235,24 +1190,21 @@ int http_request_parse(server *srv, connection *con) {
- if (con->request.http_version == HTTP_VERSION_1_0) {
- log_error_write(srv, __FILE__, __LINE__, "s",
- "HTTP/1.0 with Transfer-Encoding (bad HTTP/1.0 proxy?) -> 400");
-- con->keep_alive = 0;
-- con->http_status = 400; /* Bad Request */
-- return 0;
-+ goto failure;
- }
-
- if (0 != strcasecmp(ds->value->ptr, "chunked")) {
- /* Transfer-Encoding might contain additional encodings,
- * which are not currently supported by lighttpd */
-- con->keep_alive = 0;
- con->http_status = 501; /* Not Implemented */
-- return 0;
-+ goto failure;
- }
-
- /* reset value for Transfer-Encoding, a hop-by-hop header,
- * which must not be blindly forwarded to backends */
- buffer_reset(ds->value); /* headers with empty values are ignored */
-
-- con_length_set = 1;
-+ state.con_length_set = 1;
- con->request.content_length = -1;
-
- /*(note: ignore whether or not Content-Length was provided)*/
-@@ -1265,27 +1217,23 @@ int http_request_parse(server *srv, connection *con) {
- case HTTP_METHOD_GET:
- case HTTP_METHOD_HEAD:
- /* content-length is forbidden for those */
-- if (con_length_set && con->request.content_length != 0) {
-+ if (state.con_length_set && con->request.content_length != 0) {
- /* content-length is missing */
- log_error_write(srv, __FILE__, __LINE__, "s",
- "GET/HEAD with content-length -> 400");
-
-- con->keep_alive = 0;
-- con->http_status = 400;
-- return 0;
-+ goto failure;
- }
- break;
- case HTTP_METHOD_POST:
- /* content-length is required for them */
-- if (!con_length_set) {
-+ if (!state.con_length_set) {
- /* content-length is missing */
- log_error_write(srv, __FILE__, __LINE__, "s",
- "POST-request, but content-length missing -> 411");
-
-- con->keep_alive = 0;
- con->http_status = 411;
-- return 0;
--
-+ goto failure;
- }
- break;
- default:
-@@ -1294,12 +1242,21 @@ int http_request_parse(server *srv, connection *con) {
-
-
- /* check if we have read post data */
-- if (con_length_set) {
-+ if (state.con_length_set) {
- /* we have content */
- if (con->request.content_length != 0) {
- return 1;
- }
- }
-
-+ return 0;
-+
-+failure:
-+ if (current_header) current_header->free((data_unset *)current_header);
-+
-+ con->keep_alive = 0;
-+ con->response.keep_alive = 0;
-+ if (!con->http_status) con->http_status = 400;
-+
- return 0;
- }