[packages/lighttpd] use-after-free header unfolding fix

glen glen at pld-linux.org
Mon Aug 27 09:44:31 CEST 2018


commit f5fbcdd0f12f01f0a9d849079c407ef5cf45775d
Author: Elan Ruusamäe <glen at pld-linux.org>
Date:   Mon Aug 27 10:37:26 2018 +0300

    use-after-free header unfolding fix

 a9e131..df8e4f.patch | 976 +++++++++++++++++++++++++++++++++++++++++++++++++++
 lighttpd.spec        |   4 +-
 2 files changed, 979 insertions(+), 1 deletion(-)
---
diff --git a/lighttpd.spec b/lighttpd.spec
index dc77eff..5ac2ff0 100644
--- a/lighttpd.spec
+++ b/lighttpd.spec
@@ -42,7 +42,7 @@ Summary:	Fast and light HTTP server
 Summary(pl.UTF-8):	Szybki i lekki serwer HTTP
 Name:		lighttpd
 Version:	1.4.50
-Release:	1
+Release:	2
 License:	BSD
 Group:		Networking/Daemons/HTTP
 Source0:	https://download.lighttpd.net/lighttpd/releases-1.4.x/%{name}-%{version}.tar.xz
@@ -120,6 +120,7 @@ Patch2:		%{name}-mod_h264_streaming.patch
 Patch3:		%{name}-branding.patch
 Patch4:		systemd.patch
 Patch5:		test-port-setup.patch
+Patch6:		a9e131..df8e4f.patch
 URL:		https://www.lighttpd.net/
 %{?with_geoip:BuildRequires:	GeoIP-devel}
 %{?with_xattr:BuildRequires:	attr-devel}
@@ -952,6 +953,7 @@ Plik monitrc do monitorowania serwera www lighttpd.
 %patch3 -p1
 %patch4 -p1
 %patch5 -p1
+%patch6 -p1
 
 rm -f src/mod_ssi_exprparser.h # bad patching: should be removed by is emptied instead
 
diff --git a/a9e131..df8e4f.patch b/a9e131..df8e4f.patch
new file mode 100644
index 0000000..12ea77c
--- /dev/null
+++ b/a9e131..df8e4f.patch
@@ -0,0 +1,976 @@
+From: =?UTF-8?Q?Stefan_B=c3=bchler?= <stbuehler at web.de>
+To: lighttpd-announce at lists.lighttpd.net
+Message-ID: <8cbf0c87-1037-8f70-aa24-b2f14c95170d at 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;
+ }
================================================================

---- gitweb:

http://git.pld-linux.org/gitweb.cgi/packages/lighttpd.git/commitdiff/f5fbcdd0f12f01f0a9d849079c407ef5cf45775d



More information about the pld-cvs-commit mailing list