# Revision 2711 Index: src/mod_cgi.c =================================================================== --- src/mod_cgi.c (.../tags/lighttpd-1.4.25) +++ src/mod_cgi.c (.../branches/lighttpd-1.4.x) @@ -747,6 +747,8 @@ } if (pipe(from_cgi_fds)) { + close(to_cgi_fds[0]); + close(to_cgi_fds[1]); log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno)); return -1; } @@ -1035,6 +1037,10 @@ case -1: /* error */ log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno)); + close(from_cgi_fds[0]); + close(from_cgi_fds[1]); + close(to_cgi_fds[0]); + close(to_cgi_fds[1]); return -1; break; default: { @@ -1181,6 +1187,7 @@ plugin_config *s = p->config_storage[0]; PATCH(cgi); + PATCH(execute_x_only); /* skip the first, the global context */ for (i = 1; i < srv->config_context->used; i++) { Index: src/base.h =================================================================== --- src/base.h (.../tags/lighttpd-1.4.25) +++ src/base.h (.../branches/lighttpd-1.4.x) @@ -431,7 +431,6 @@ #ifdef USE_OPENSSL SSL *ssl; - buffer *ssl_error_want_reuse_buffer; # ifndef OPENSSL_NO_TLSEXT buffer *tlsext_server_name; # endif Index: src/mod_rewrite.c =================================================================== --- src/mod_rewrite.c (.../tags/lighttpd-1.4.25) +++ src/mod_rewrite.c (.../branches/lighttpd-1.4.x) @@ -394,7 +394,7 @@ buffer_reset(con->request.uri); start = 0; - for (k = 0; k < pattern_len; k++) { + for (k = 0; k+1 < pattern_len; k++) { if (pattern[k] == '$' || pattern[k] == '%') { /* got one */ Index: src/connections.c =================================================================== --- src/connections.c (.../tags/lighttpd-1.4.25) +++ src/connections.c (.../branches/lighttpd-1.4.x) @@ -192,40 +192,42 @@ static int connection_handle_read_ssl(server *srv, connection *con) { #ifdef USE_OPENSSL - int r, ssl_err, len, count = 0; + int r, ssl_err, len, count = 0, read_offset, toread; buffer *b = NULL; if (!con->conf.is_ssl) return -1; - /* don't resize the buffer if we were in SSL_ERROR_WANT_* */ - ERR_clear_error(); do { - if (!con->ssl_error_want_reuse_buffer) { - b = buffer_init(); - buffer_prepare_copy(b, SSL_pending(con->ssl) + (16 * 1024)); /* the pending bytes + 16kb */ + if (NULL != con->read_queue->last) { + b = con->read_queue->last->mem; + } + if (NULL == b || b->size - b->used < 1024) { + b = chunkqueue_get_append_buffer(con->read_queue); + len = SSL_pending(con->ssl); + if (len < 4*1024) len = 4*1024; /* always alloc >= 4k buffer */ + buffer_prepare_copy(b, len + 1); + /* overwrite everything with 0 */ memset(b->ptr, 0, b->size); - } else { - b = con->ssl_error_want_reuse_buffer; } - len = SSL_read(con->ssl, b->ptr, b->size - 1); - con->ssl_error_want_reuse_buffer = NULL; /* reuse it only once */ + read_offset = (b->used > 0) ? b->used - 1 : 0; + toread = b->size - 1 - read_offset; + len = SSL_read(con->ssl, b->ptr + read_offset, toread); + if (len > 0) { - b->used = len; + if (b->used > 0) b->used--; + b->used += len; b->ptr[b->used++] = '\0'; - /* we move the buffer to the chunk-queue, no need to free it */ + con->bytes_read += len; - chunkqueue_append_buffer_weak(con->read_queue, b); count += len; - con->bytes_read += len; - b = NULL; } - } while (len > 0 && count < MAX_READ_LIMIT); + } while (len == toread && count < MAX_READ_LIMIT); if (len < 0) { @@ -234,11 +236,11 @@ case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: con->is_readable = 0; - con->ssl_error_want_reuse_buffer = b; - b = NULL; + /* the manual says we have to call SSL_read with the same arguments next time. + * we ignore this restriction; no one has complained about it in 1.5 yet, so it probably works anyway. + */ - /* we have to steal the buffer from the queue-queue */ return 0; case SSL_ERROR_SYSCALL: /** @@ -297,16 +299,11 @@ connection_set_state(srv, con, CON_STATE_ERROR); - buffer_free(b); - return -1; } else if (len == 0) { con->is_readable = 0; /* the other end close the connection -> KEEP-ALIVE */ - /* pipelining */ - buffer_free(b); - return -2; } @@ -321,26 +318,41 @@ static int connection_handle_read(server *srv, connection *con) { int len; buffer *b; - int toread; + int toread, read_offset; if (con->conf.is_ssl) { return connection_handle_read_ssl(srv, con); } + b = (NULL != con->read_queue->last) ? con->read_queue->last->mem : NULL; + + /* default size for chunks is 4kb; only use bigger chunks if FIONREAD tells + * us more than 4kb is available + * if FIONREAD doesn't signal a big chunk we fill the previous buffer + * if it has >= 1kb free + */ #if defined(__WIN32) - b = chunkqueue_get_append_buffer(con->read_queue); - buffer_prepare_copy(b, 4 * 1024); - len = recv(con->fd, b->ptr, b->size - 1, 0); -#else - if (ioctl(con->fd, FIONREAD, &toread) || toread == 0) { + if (NULL == b || b->size - b->used < 1024) { b = chunkqueue_get_append_buffer(con->read_queue); buffer_prepare_copy(b, 4 * 1024); + } + + read_offset = (b->used == 0) ? 0 : b->used - 1; + len = recv(con->fd, b->ptr + read_offset, b->size - 1 - read_offset, 0); +#else + if (ioctl(con->fd, FIONREAD, &toread) || toread == 0 || toread <= 4*1024) { + if (NULL == b || b->size - b->used < 1024) { + b = chunkqueue_get_append_buffer(con->read_queue); + buffer_prepare_copy(b, 4 * 1024); + } } else { if (toread > MAX_READ_LIMIT) toread = MAX_READ_LIMIT; b = chunkqueue_get_append_buffer(con->read_queue); buffer_prepare_copy(b, toread + 1); } - len = read(con->fd, b->ptr, b->size - 1); + + read_offset = (b->used == 0) ? 0 : b->used - 1; + len = read(con->fd, b->ptr + read_offset, b->size - 1 - read_offset); #endif if (len < 0) { @@ -374,7 +386,8 @@ con->is_readable = 0; } - b->used = len; + if (b->used > 0) b->used--; + b->used += len; b->ptr[b->used++] = '\0'; con->bytes_read += len; @@ -850,13 +863,6 @@ /* The cond_cache gets reset in response.c */ /* config_cond_cache_reset(srv, con); */ -#ifdef USE_OPENSSL - if (con->ssl_error_want_reuse_buffer) { - buffer_free(con->ssl_error_want_reuse_buffer); - con->ssl_error_want_reuse_buffer = NULL; - } -#endif - con->header_len = 0; con->in_error_handler = 0; @@ -945,62 +951,50 @@ last_chunk = NULL; last_offset = 0; - for (c = cq->first; !last_chunk && c; c = c->next) { + for (c = cq->first; c; c = c->next) { buffer b; size_t i; b.ptr = c->mem->ptr + c->offset; b.used = c->mem->used - c->offset; + if (b.used > 0) b.used--; /* buffer "used" includes terminating zero */ - for (i = 0; !last_chunk && i < b.used; i++) { + for (i = 0; i < b.used; i++) { char ch = b.ptr[i]; - size_t have_chars = 0; - switch (ch) { - case '\r': - /* we have to do a 4 char lookup */ - have_chars = b.used - i - 1; + if ('\r' == ch) { + /* chec if \n\r\n follows */ + size_t j = i+1; + chunk *cc = c; + const char header_end[] = "\r\n\r\n"; + int header_end_match_pos = 1; - if (have_chars >= 4) { - /* all chars are in this buffer */ + for ( ; cc; cc = cc->next, j = 0 ) { + buffer bb; + bb.ptr = cc->mem->ptr + cc->offset; + bb.used = cc->mem->used - cc->offset; + if (bb.used > 0) bb.used--; /* buffer "used" includes terminating zero */ - if (0 == strncmp(b.ptr + i, "\r\n\r\n", 4)) { - /* found */ - last_chunk = c; - last_offset = i + 4; + for ( ; j < bb.used; j++) { + ch = bb.ptr[j]; - break; - } - } else { - chunk *lookahead_chunk = c->next; - size_t missing_chars; - /* looks like the following chars are not in the same chunk */ - - missing_chars = 4 - have_chars; - - if (lookahead_chunk && lookahead_chunk->type == MEM_CHUNK) { - /* is the chunk long enough to contain the other chars ? */ - - if (lookahead_chunk->mem->used > missing_chars) { - if (0 == strncmp(b.ptr + i, "\r\n\r\n", have_chars) && - 0 == strncmp(lookahead_chunk->mem->ptr, "\r\n\r\n" + have_chars, missing_chars)) { - - last_chunk = lookahead_chunk; - last_offset = missing_chars; - - break; + if (ch == header_end[header_end_match_pos]) { + header_end_match_pos++; + if (4 == header_end_match_pos) { + last_chunk = cc; + last_offset = j+1; + goto found_header_end; } } else { - /* a splited \r \n */ - break; + goto reset_search; } } } - - break; } +reset_search: ; } } +found_header_end: /* found */ if (last_chunk) { @@ -1140,8 +1134,15 @@ } else { buffer *b; - b = chunkqueue_get_append_buffer(dst_cq); - buffer_copy_string_len(b, c->mem->ptr + c->offset, toRead); + if (dst_cq->last && + dst_cq->last->type == MEM_CHUNK) { + b = dst_cq->last->mem; + } else { + b = chunkqueue_get_append_buffer(dst_cq); + /* prepare buffer size for remaining POST data; is < 64kb */ + buffer_prepare_copy(b, con->request.content_length - dst_cq->bytes_in + 1); + } + buffer_append_string_len(b, c->mem->ptr + c->offset, toRead); } c->offset += toRead; Index: src/chunk.c =================================================================== --- src/chunk.c (.../tags/lighttpd-1.4.25) +++ src/chunk.c (.../branches/lighttpd-1.4.x) @@ -197,8 +197,6 @@ int chunkqueue_append_buffer_weak(chunkqueue *cq, buffer *mem) { chunk *c; - if (mem->used == 0) return 0; - c = chunkqueue_get_unused_chunk(cq); c->type = MEM_CHUNK; c->offset = 0; Index: src/mod_proxy.c =================================================================== --- src/mod_proxy.c (.../tags/lighttpd-1.4.25) +++ src/mod_proxy.c (.../branches/lighttpd-1.4.x) @@ -1047,12 +1047,33 @@ * */ - proxy_connection_close(srv, hctx); - joblist_append(srv, con); + if (hctx->host) { + hctx->host->is_disabled = 1; + hctx->host->disable_ts = srv->cur_ts; + log_error_write(srv, __FILE__, __LINE__, "sbdd", "proxy-server disabled:", + hctx->host->host, + hctx->host->port, + hctx->fd); - con->http_status = 503; - con->mode = DIRECT; + /* disable this server */ + hctx->host->is_disabled = 1; + hctx->host->disable_ts = srv->cur_ts; + proxy_connection_close(srv, hctx); + + /* reset the enviroment and restart the sub-request */ + buffer_reset(con->physical.path); + con->mode = DIRECT; + + joblist_append(srv, con); + } else { + proxy_connection_close(srv, hctx); + joblist_append(srv, con); + + con->mode = DIRECT; + con->http_status = 503; + } + return HANDLER_FINISHED; } Index: src/mod_redirect.c =================================================================== --- src/mod_redirect.c (.../tags/lighttpd-1.4.25) +++ src/mod_redirect.c (.../branches/lighttpd-1.4.x) @@ -210,7 +210,7 @@ buffer_reset(p->location); start = 0; - for (k = 0; k < pattern_len; k++) { + for (k = 0; k + 1 < pattern_len; k++) { if (pattern[k] == '$' || pattern[k] == '%') { /* got one */ Index: src/mod_fastcgi.c =================================================================== --- src/mod_fastcgi.c (.../tags/lighttpd-1.4.25) +++ src/mod_fastcgi.c (.../branches/lighttpd-1.4.x) @@ -2307,6 +2307,9 @@ filename = pos; if (NULL == (range = strchr(pos, ' '))) { /* missing range */ + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "ss", "Couldn't find range after filename:", filename); + } return 1; } buffer_copy_string_len(srv->tmp_buf, filename, range - filename); @@ -2338,14 +2341,24 @@ char *rpos = NULL; errno = 0; begin_range = strtoll(range, &rpos, 10); - if (errno != 0 || begin_range < 0 || rpos == range) return 1; - if ('-' != *rpos++) return 1; + if (errno != 0 || begin_range < 0 || rpos == range) goto range_failed; + if ('-' != *rpos++) goto range_failed; if (rpos != pos) { range = rpos; end_range = strtoll(range, &rpos, 10); - if (errno != 0 || end_range < 0 || rpos == range) return 1; + if (errno != 0 || end_range < 0 || rpos == range) goto range_failed; } - if (rpos != pos) return 1; + if (rpos != pos) goto range_failed; + + goto range_success; + +range_failed: + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "ss", "Couldn't decode range after filename:", filename); + } + return 1; + +range_success: ; } /* no parameters accepted */ Index: src/mod_accesslog.c =================================================================== --- src/mod_accesslog.c (.../tags/lighttpd-1.4.25) +++ src/mod_accesslog.c (.../branches/lighttpd-1.4.x) @@ -788,6 +788,13 @@ buffer_append_string_len(b, CONST_STR_LEN("-")); } break; + case FORMAT_ENV: + if (NULL != (ds = (data_string *)array_get_element(con->environment, p->conf.parsed_format->ptr[j]->string->ptr))) { + accesslog_append_escaped(b, ds->value); + } else { + buffer_append_string_len(b, CONST_STR_LEN("-")); + } + break; case FORMAT_FILENAME: if (con->physical.path->used > 1) { buffer_append_string_buffer(b, con->physical.path); @@ -864,7 +871,6 @@ { 'A', FORMAT_LOCAL_ADDR }, { 'C', FORMAT_COOKIE }, { 'D', FORMAT_TIME_USED_MS }, - { 'e', FORMAT_ENV }, */ break; Index: tests/request.t =================================================================== --- tests/request.t (.../tags/lighttpd-1.4.25) +++ tests/request.t (.../branches/lighttpd-1.4.x) @@ -8,7 +8,7 @@ use strict; use IO::Socket; -use Test::More tests => 41; +use Test::More tests => 42; use LightyTest; my $tf = LightyTest->new(); @@ -389,5 +389,14 @@ $t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 304, '-Content-Length' => '' } ]; ok($tf->handle_http($t) == 0, 'Status 304 has no Content-Length (#1002)'); +$t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain' } ]; +$t->{SLOWREQUEST} = 1; +ok($tf->handle_http($t) == 0, 'GET, slow \\r\\n\\r\\n (#2105)'); + ok($tf->stop_proc == 0, "Stopping lighttpd"); Index: tests/LightyTest.pm =================================================================== --- tests/LightyTest.pm (.../tags/lighttpd-1.4.25) +++ tests/LightyTest.pm (.../branches/lighttpd-1.4.x) @@ -76,7 +76,7 @@ kill('TERM', $pid) or return -1; return -1 if ($pid != waitpid($pid, 0)); } else { - diag("Process not started, nothing to stop"); + diag("\nProcess not started, nothing to stop"); return -1; } @@ -98,7 +98,7 @@ return -1; } if (0 >= $timeout) { - diag("Timeout while trying to connect; killing child"); + diag("\nTimeout while trying to connect; killing child"); kill('TERM', $child); return -1; } @@ -128,10 +128,10 @@ } elsif (defined $ENV{"TRACEME"} && $ENV{"TRACEME"} eq 'valgrind') { $cmdline = "valgrind --tool=memcheck --show-reachable=yes --leak-check=yes --log-file=valgrind ".$cmdline; } - # diag("starting lighttpd at :".$self->{PORT}.", cmdline: ".$cmdline ); + # diag("\nstarting lighttpd at :".$self->{PORT}.", cmdline: ".$cmdline ); my $child = fork(); if (not defined $child) { - diag("Fork failed"); + diag("\nFork failed"); return -1; } if ($child == 0) { @@ -139,7 +139,7 @@ } if (0 != $self->wait_for_port_with_proc($self->{PORT}, $child)) { - diag(sprintf('The process %i is not up', $child)); + diag(sprintf('\nThe process %i is not up', $child)); return -1; } @@ -157,6 +157,7 @@ my @request = $t->{REQUEST}; my @response = $t->{RESPONSE}; + my $slow = defined $t->{SLOWREQUEST}; my $is_debug = $ENV{"TRACE_HTTP"}; my $remote = @@ -165,33 +166,56 @@ PeerPort => $self->{PORT}); if (not defined $remote) { - diag("connect failed: $!"); + diag("\nconnect failed: $!"); return -1; } $remote->autoflush(1); - diag("sending request header to ".$host.":".$self->{PORT}) if $is_debug; - foreach(@request) { - # pipeline requests - s/\r//g; - s/\n/$EOL/g; + if (!$slow) { + diag("\nsending request header to ".$host.":".$self->{PORT}) if $is_debug; + foreach(@request) { + # pipeline requests + s/\r//g; + s/\n/$EOL/g; - print $remote $_.$BLANK; - diag("<< ".$_) if $is_debug; + print $remote $_.$BLANK; + diag("\n<< ".$_) if $is_debug; + } + shutdown($remote, 1); # I've stopped writing data + } else { + diag("\nsending request header to ".$host.":".$self->{PORT}) if $is_debug; + foreach(@request) { + # pipeline requests + chomp; + s/\r//g; + s/\n/$EOL/g; + + print $remote $_; + diag("<< ".$_."\n") if $is_debug; + select(undef, undef, undef, 0.1); + print $remote "\015"; + select(undef, undef, undef, 0.1); + print $remote "\012"; + select(undef, undef, undef, 0.1); + print $remote "\015"; + select(undef, undef, undef, 0.1); + print $remote "\012"; + select(undef, undef, undef, 0.1); + } + } - shutdown($remote, 1); # I've stopped writing data - diag("... done") if $is_debug; + diag("\n... done") if $is_debug; my $lines = ""; - diag("receiving response") if $is_debug; + diag("\nreceiving response") if $is_debug; # read everything while(<$remote>) { $lines .= $_; diag(">> ".$_) if $is_debug; } - diag("... done") if $is_debug; + diag("\n... done") if $is_debug; close $remote; @@ -209,7 +233,7 @@ (my $line, $lines) = split($EOL, $lines, 2); # header finished - last if(length($line) == 0); + last if(!defined $line or length($line) == 0); if ($ln == 0) { # response header @@ -221,21 +245,21 @@ (my $h = $1) =~ tr/[A-Z]/[a-z]/; if (defined $resp_hdr{$h}) { -# diag(sprintf("header '%s' is duplicated: '%s' and '%s'\n", +# diag(sprintf("\nheader '%s' is duplicated: '%s' and '%s'\n", # $h, $resp_hdr{$h}, $2)); $resp_hdr{$h} .= ', '.$2; } else { $resp_hdr{$h} = $2; } } else { - diag(sprintf("unexpected line '%s'\n", $line)); + diag(sprintf("\nunexpected line '%s'", $line)); return -1; } } } if (not defined($resp_line)) { - diag(sprintf("empty response\n")); + diag(sprintf("\nempty response")); return -1; } @@ -259,29 +283,29 @@ # check conditions if ($resp_line =~ /^(HTTP\/1\.[01]) ([0-9]{3}) .+$/) { if ($href->{'HTTP-Protocol'} ne $1) { - diag(sprintf("proto failed: expected '%s', got '%s'\n", $href->{'HTTP-Protocol'}, $1)); + diag(sprintf("\nproto failed: expected '%s', got '%s'", $href->{'HTTP-Protocol'}, $1)); return -1; } if ($href->{'HTTP-Status'} ne $2) { - diag(sprintf("status failed: expected '%s', got '%s'\n", $href->{'HTTP-Status'}, $2)); + diag(sprintf("\nstatus failed: expected '%s', got '%s'", $href->{'HTTP-Status'}, $2)); return -1; } } else { - diag(sprintf("unexpected resp_line '%s'\n", $resp_line)); + diag(sprintf("\nunexpected resp_line '%s'", $resp_line)); return -1; } if (defined $href->{'HTTP-Content'}) { $resp_body = "" unless defined $resp_body; if ($href->{'HTTP-Content'} ne $resp_body) { - diag(sprintf("body failed: expected '%s', got '%s'\n", $href->{'HTTP-Content'}, $resp_body)); + diag(sprintf("\nbody failed: expected '%s', got '%s'", $href->{'HTTP-Content'}, $resp_body)); return -1; } } if (defined $href->{'-HTTP-Content'}) { if (defined $resp_body && $resp_body ne '') { - diag(sprintf("body failed: expected empty body, got '%s'\n", $resp_body)); + diag(sprintf("\nbody failed: expected empty body, got '%s'", $resp_body)); return -1; } } @@ -309,12 +333,12 @@ if ($key_inverted) { if (defined $resp_hdr{$k}) { - diag(sprintf("header '%s' MUST not be set\n", $k)); + diag(sprintf("\nheader '%s' MUST not be set", $k)); return -1; } } else { if (not defined $resp_hdr{$k}) { - diag(sprintf("required header '%s' is missing\n", $k)); + diag(sprintf("\nrequired header '%s' is missing", $k)); return -1; } } @@ -322,12 +346,12 @@ if ($verify_value) { if ($href->{$_} =~ /^\/(.+)\/$/) { if ($resp_hdr{$k} !~ /$1/) { - diag(sprintf("response-header failed: expected '%s', got '%s', regex: %s\n", + diag(sprintf("\nresponse-header failed: expected '%s', got '%s', regex: %s", $href->{$_}, $resp_hdr{$k}, $1)); return -1; } } elsif ($href->{$_} ne $resp_hdr{$k}) { - diag(sprintf("response-header failed: expected '%s', got '%s'\n", + diag(sprintf("\nresponse-header failed: expected '%s', got '%s'", $href->{$_}, $resp_hdr{$k})); return -1; } @@ -337,7 +361,7 @@ # we should have sucked up everything if (defined $lines) { - diag(sprintf("unexpected lines '%s'\n", $lines)); + diag(sprintf("\nunexpected lines '%s'", $lines)); return -1; } @@ -348,7 +372,7 @@ my ($self, $binary, $port) = @_; my $child = fork(); if (not defined $child) { - diag("Couldn't fork\n"); + diag("\nCouldn't fork"); return -1; } if ($child == 0) { @@ -362,7 +386,7 @@ exec $binary or die($?); } else { if (0 != $self->wait_for_port_with_proc($port, $child)) { - diag(sprintf('The process %i is not up (port %i, %s)', $child, $port, $binary)); + diag(sprintf("\nThe process %i is not up (port %i, %s)", $child, $port, $binary)); return -1; } return $child; Index: tests/run-tests.pl =================================================================== --- tests/run-tests.pl (.../tags/lighttpd-1.4.25) +++ tests/run-tests.pl (.../branches/lighttpd-1.4.x) @@ -17,4 +17,4 @@ } } closedir DIR; -runtests @fs; +runtests (sort @fs); Index: configure.ac =================================================================== Index: SConstruct =================================================================== Index: NEWS =================================================================== --- NEWS (.../tags/lighttpd-1.4.25) +++ NEWS (.../branches/lighttpd-1.4.x) @@ -3,7 +3,18 @@ NEWS ==== -- 1.4.25 - +- 1.4.26 - + * Fix request parser to handle packets with splitted \r\n\r\n (fixes #2105) + * Remove dependency on automake >= 1.11 with m4_ifdef check + * mod_accesslog: support %e (fixes #2113, thx presbrey) + * Fix mod_cgi cgi.execute-x-only option in global block + * mod_fastcgi: x-sendfile2 parse error debugging + * Fix mod_proxy dead host detection if connect() fails + * Fix fd leaks in mod_cgi (fds not closed on pipe/fork failures, found by Rodrigo, fixes #2158, #2159) + * Fix segfault with broken rewrite/redirect patterns (fixes #2140, found by crypt) + * Append to previous buffer in con read (fixes #2147, found by liming, CVE-2010-0295) + +- 1.4.25 - 2009-11-21 * mod_magnet: fix pairs() for normal tables and strings (fixes #1307) * mod_magnet: add traceback for printing lua errors * mod_rewrite: fix compile error if compiled without pcre Index: CMakeLists.txt ===================================================================