From f0333c8c0d0294e087b6a18ed0549d448a82065d Mon Sep 17 00:00:00 2001 From: Jan Kneschke Date: Fri, 17 Aug 2007 21:04:20 +0000 Subject: [PATCH] fixed crash on mixed \r\n and \n sequences git-svn-id: svn://svn.lighttpd.net/lighttpd/branches/lighttpd-1.4.x@1925 152afb58-edef-0310-8abb-c4023f1b3aa9 --- NEWS | 2 + src/mod_cgi.c | 144 ++++++++++++++++++++++++++++++------------------ tests/mod-cgi.t | 3 +- 3 files changed, 94 insertions(+), 55 deletions(-) diff --git a/NEWS b/NEWS index 58e2b64b..689b5e67 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,8 @@ NEWS * fixed different ETag length on 32/64 platforms (#1279) * fixed compression of files < 128 bytes by disabling compression (#1241) * fixed mysql server reconnects (#518) + * fixed disabled keep-alive for dynamic content with HTTP/1.0 (#1166) + * fixed crash on mixed EOL sequences in mod_cgi - 1.4.16 - diff --git a/src/mod_cgi.c b/src/mod_cgi.c index 7da98154..707b26b0 100644 --- a/src/mod_cgi.c +++ b/src/mod_cgi.c @@ -222,7 +222,7 @@ static int cgi_pid_del(server *srv, plugin_data *p, pid_t pid) { return 0; } -static int cgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in, int eol) { +static int cgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in) { char *ns; const char *s; int line = 0; @@ -232,14 +232,17 @@ static int cgi_response_parse(server *srv, connection *con, plugin_data *p, buff buffer_copy_string_buffer(p->parse_response, in); for (s = p->parse_response->ptr; - NULL != (ns = (eol == EOL_RN ? strstr(s, "\r\n") : strchr(s, '\n'))); - s = ns + (eol == EOL_RN ? 2 : 1), line++) { + NULL != (ns = strchr(s, '\n')); + s = ns + 1, line++) { const char *key, *value; int key_len; data_string *ds; + /* strip the \n */ ns[0] = '\0'; + if (ns > s && ns[-1] == '\r') ns[-1] = '\0'; + if (line == 0 && 0 == strncmp(s, "HTTP/1.", 7)) { /* non-parsed header ... we parse them anyway */ @@ -260,7 +263,7 @@ static int cgi_response_parse(server *srv, connection *con, plugin_data *p, buff } } } else { - + /* parse the headers */ key = s; if (NULL == (value = strchr(s, ':'))) { /* we expect: ": \r\n" */ @@ -362,63 +365,81 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) { /* split header from body */ if (con->file_started == 0) { - char *c; - int in_header = 0; - int header_end = 0; - int cp, eol = EOL_UNSET; - size_t used = 0; + int is_header = 0; + int is_header_end = 0; + size_t last_eol = 0; + size_t i; buffer_append_string_buffer(hctx->response_header, hctx->response); + /** + * we have to handle a few cases: + * + * nph: + * + * HTTP/1.0 200 Ok\n + * Header: Value\n + * \n + * + * CGI: + * Header: Value\n + * Status: 200\n + * \n + * + * and different mixes of \n and \r\n combinations + * + * Some users also forget about CGI and just send a response and hope + * we handle it. No headers, no header-content seperator + * + */ + /* nph (non-parsed headers) */ - if (0 == strncmp(hctx->response_header->ptr, "HTTP/1.", 7)) in_header = 1; - - /* search for the \r\n\r\n or \n\n in the string */ - for (c = hctx->response_header->ptr, cp = 0, used = hctx->response_header->used - 1; used; c++, cp++, used--) { - if (*c == ':') in_header = 1; - else if (*c == '\n') { - if (in_header == 0) { - /* got a response without a response header */ - - c = NULL; - header_end = 1; - break; - } + if (0 == strncmp(hctx->response_header->ptr, "HTTP/1.", 7)) is_header = 1; + + for (i = 0; !is_header_end && i < hctx->response_header->used - 1; i++) { + char c = hctx->response_header->ptr[i]; + + switch (c) { + case ':': + /* we found a colon + * + * looks like we have a normal header + */ + is_header = 1; + break; + case '\n': + /* EOL */ + if (is_header == 0) { + /* we got a EOL but we don't seem to got a HTTP header */ - if (eol == EOL_UNSET) eol = EOL_N; + is_header_end = 1; - if (*(c+1) == '\n') { - header_end = 1; break; } - } else if (used > 1 && *c == '\r' && *(c+1) == '\n') { - if (in_header == 0) { - /* got a response without a response header */ - - c = NULL; - header_end = 1; + /** + * check if we saw a \n(\r)?\n sequence + */ + if (last_eol > 0 && + ((i - last_eol == 1) || + (i - last_eol == 2 && hctx->response_header->ptr[i - 1] == '\r'))) { + log_error_write(srv, __FILE__, __LINE__, + "sdd", + "EOL at", + i, last_eol + ); + is_header_end = 1; break; } - if (eol == EOL_UNSET) eol = EOL_RN; + last_eol = i; - if (used > 3 && - *(c+2) == '\r' && - *(c+3) == '\n') { - header_end = 1; - break; - } - - /* skip the \n */ - c++; - cp++; - used--; + break; } } - if (header_end) { - if (c == NULL) { + if (is_header_end) { + if (!is_header) { /* no header, but a body */ if (con->request.http_version == HTTP_VERSION_1_1) { @@ -428,15 +449,30 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) { http_chunk_append_mem(srv, con, hctx->response_header->ptr, hctx->response_header->used); joblist_append(srv, con); } else { - size_t hlen = c - hctx->response_header->ptr + (eol == EOL_RN ? 4 : 2); - size_t blen = hctx->response_header->used - hlen - 1; - - /* a small hack: terminate after at the second \r */ - hctx->response_header->used = hlen + 1 - (eol == EOL_RN ? 2 : 1); - hctx->response_header->ptr[hlen - (eol == EOL_RN ? 2 : 1)] = '\0'; + const char *bstart; + size_t blen; + + /** + * i still points to the char after the terminating EOL EOL + * + * put it on the last \n again + */ + i--; + + /* the body starts after the EOL */ + bstart = hctx->response_header->ptr + (i + 1); + blen = (hctx->response_header->used - 1) - (i + 1); + + /* string the last \r?\n */ + if (i > 0 && (hctx->response_header->ptr[i - 1] == '\r')) { + i--; + } + hctx->response_header->ptr[i] = '\0'; + hctx->response_header->used = i + 1; /* the string + \0 */ + /* parse the response header */ - cgi_response_parse(srv, con, p, hctx->response_header, eol); + cgi_response_parse(srv, con, p, hctx->response_header); /* enable chunked-transfer-encoding */ if (con->request.http_version == HTTP_VERSION_1_1 && @@ -444,8 +480,8 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) { con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; } - if ((hctx->response->used != hlen) && blen > 0) { - http_chunk_append_mem(srv, con, c + (eol == EOL_RN ? 4: 2), blen + 1); + if (blen > 0) { + http_chunk_append_mem(srv, con, bstart, blen + 1); joblist_append(srv, con); } } diff --git a/tests/mod-cgi.t b/tests/mod-cgi.t index 32125419..f3952879 100755 --- a/tests/mod-cgi.t +++ b/tests/mod-cgi.t @@ -115,12 +115,13 @@ EOF ); $t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, '+Content-Length' => '' } ]; ok($tf->handle_http($t) == 0, 'cgi-env: HTTP_HOST'); + # broken header crash $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 500 } ]; +$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 302, 'Location' => 'http://www.example.org/' } ]; ok($tf->handle_http($t) == 0, 'broken header via perl cgi'); ok($tf->stop_proc == 0, "Stopping lighttpd");