Subject: [PATCH] Append to previous buffer in con read (fixes #2147, found by liming)

 * Remove ssl_error_want_reuse_buffer for SSL_read:
   Although the manual states we have to use the same arguments in the
   next call after SSL_ERROR_WANT_*, it has been running without this
   in 1.5 for a long time now.
 * As POST-data chunks get copied to the next queue, we reuse chunks
   there as well.
---
 src/base.h        |    1 -
 src/chunk.c       |    2 -
 src/connections.c |   94 ++++++++++++++++++++++++++++++-----------------------
 3 files changed, 53 insertions(+), 44 deletions(-)

Index: src/base.h
===================================================================
--- src/base.h.orig	2008-08-19 19:40:42.000000000 +0200
+++ src/base.h	2012-01-27 12:09:29.876756288 +0100
@@ -415,7 +415,6 @@ typedef struct {
 
 #ifdef USE_OPENSSL
 	SSL *ssl;
-	buffer *ssl_error_want_reuse_buffer;
 #endif
 	/* etag handling */
 	etag_flags_t etag_flags;
Index: src/chunk.c
===================================================================
--- src/chunk.c.orig	2008-07-30 21:38:32.000000000 +0200
+++ src/chunk.c	2012-01-27 12:09:29.878756288 +0100
@@ -197,8 +197,6 @@ int chunkqueue_append_buffer(chunkqueue
 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/connections.c
===================================================================
--- src/connections.c.orig	2008-08-19 19:40:42.000000000 +0200
+++ src/connections.c	2012-01-27 12:09:29.880756288 +0100
@@ -192,39 +192,41 @@ static void dump_packet(const unsigned c
 
 static int connection_handle_read_ssl(server *srv, connection *con) {
 #ifdef USE_OPENSSL
-	int r, ssl_err, len;
+	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 */
-
-			chunkqueue_append_buffer_weak(con->read_queue, b);
 			con->bytes_read += len;
-			b = NULL;
+			count += len;
 		}
-	} while (len > 0);
+	} while (len == toread && count < MAX_READ_LIMIT);
 
 
 	if (len < 0) {
@@ -232,11 +234,11 @@ static int connection_handle_read_ssl(se
 		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:
 			/**
@@ -295,16 +297,11 @@ static int connection_handle_read_ssl(se
 
 		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;
 	}
 
@@ -319,27 +316,41 @@ static int connection_handle_read_ssl(se
 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);
+	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)) {
-		log_error_write(srv, __FILE__, __LINE__, "sd",
-				"unexpected end-of-file:",
-				con->fd);
-		return -1;
+	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);
 	}
-	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) {
@@ -373,7 +384,8 @@ static int connection_handle_read(server
 		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;
@@ -840,13 +852,6 @@ int connection_reset(server *srv, connec
 	/* 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;
 
@@ -1127,8 +1132,15 @@ int connection_handle_read_state(server
 			} 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/settings.h
===================================================================
--- src/settings.h.orig	2006-10-04 15:26:23.000000000 +0200
+++ src/settings.h	2012-01-27 12:09:29.881756288 +0100
@@ -13,6 +13,7 @@
  * 64kB (no real reason, just a guess)
  */
 #define BUFFER_MAX_REUSE_SIZE  (4 * 1024)
+#define MAX_READ_LIMIT (4*1024*1024)
 
 /**
  * max size of the HTTP request header
