text: #include <sys/stat.h>
  
  #include <stdlib.h>
  #include <stdio.h>
  #include <unistd.h>
  #include <errno.h>
  #include <string.h>
  #include <fcntl.h>
  #include <assert.h>
  
  #include "buffer.h"
  #include "server.h"
  #include "log.h"
  #include "connections.h"
  #include "fdevent.h"
  
  #include "request.h"
  #include "response.h"
  #include "network.h"
  #include "http_chunk.h"
  #include "stat_cache.h"
  #include "joblist.h"
  
  #include "plugin.h"
  
  #include "inet_ntop_cache.h"
  
  #ifdef USE_OPENSSL
  # include <openssl/ssl.h> 
  # include <openssl/err.h> 
  #endif
  
  #ifdef HAVE_SYS_FILIO_H
  # include <sys/filio.h>
  #endif
  
  #include "sys-socket.h"
  
  typedef struct {
  	        PLUGIN_DATA;
  } plugin_data;
  
  static connection *connections_get_new_connection(server *srv) {
  	connections *conns = srv->conns;
  	size_t i;
  	
  	if (conns->size == 0) {
  		conns->size = 128;
  		conns->ptr = NULL;
  		conns->ptr = malloc(sizeof(*conns->ptr) * conns->size);
  		for (i = 0; i < conns->size; i++) {
  			conns->ptr[i] = connection_init(srv);
  		}
  	} else if (conns->size == conns->used) {
  		conns->size += 128;
  		conns->ptr = realloc(conns->ptr, sizeof(*conns->ptr) * conns->size);
  		
  		for (i = conns->used; i < conns->size; i++) {
  			conns->ptr[i] = connection_init(srv);
  		}
  	}
  
  	connection_reset(srv, conns->ptr[conns->used]);
  #if 0	
  	fprintf(stderr, "%s.%d: add: ", __FILE__, __LINE__);
  	for (i = 0; i < conns->used + 1; i++) {
  		fprintf(stderr, "%d ", conns->ptr[i]->fd);
  	}
  	fprintf(stderr, "\n");
  #endif	
  	
  	conns->ptr[conns->used]->ndx = conns->used;
  	return conns->ptr[conns->used++];
  }
  
  static int connection_del(server *srv, connection *con) {
  	size_t i;
  	connections *conns = srv->conns;
  	connection *temp;
  	
  	if (con == NULL) return -1;
  	
  	if (-1 == con->ndx) return -1;
  	
  	i = con->ndx;
  	
  	/* not last element */
  	
  	if (i != conns->used - 1) {
  		temp = conns->ptr[i];
  		conns->ptr[i] = conns->ptr[conns->used - 1];
  		conns->ptr[conns->used - 1] = temp;
  		
  		conns->ptr[i]->ndx = i;
  		conns->ptr[conns->used - 1]->ndx = -1;
  	}
  	
  	conns->used--;
  	
  	con->ndx = -1;
  #if 0
  	fprintf(stderr, "%s.%d: del: (%d)", __FILE__, __LINE__, conns->used);
  	for (i = 0; i < conns->used; i++) {
  		fprintf(stderr, "%d ", conns->ptr[i]->fd);
  	}
  	fprintf(stderr, "\n");
  #endif	
  	return 0;
  }
  
  int connection_close(server *srv, connection *con) {
  #ifdef USE_OPENSSL
  	server_socket *srv_sock = con->srv_socket;
  #endif
  	
  #ifdef USE_OPENSSL
  	if (srv_sock->is_ssl) {
  		if (con->ssl) SSL_free(con->ssl);
  		con->ssl = NULL;
  	}
  #endif
  	
  	fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
  	fdevent_unregister(srv->ev, con->fd);
  #ifdef __WIN32
  	if (closesocket(con->fd)) {
  		log_error_write(srv, __FILE__, __LINE__, "sds",
  				"(warning) close:", con->fd, strerror(errno));
  	}
  #else
  	if (close(con->fd)) {
  		log_error_write(srv, __FILE__, __LINE__, "sds",
  				"(warning) close:", con->fd, strerror(errno));
  	}
  #endif
  	
  	srv->cur_fds--;
  #if 0
  	log_error_write(srv, __FILE__, __LINE__, "sd",
  			"closed()", con->fd);
  #endif
  	
  	connection_del(srv, con);
  	connection_set_state(srv, con, CON_STATE_CONNECT);
  	
  	return 0;
  }
  
  #if 0
  static void dump_packet(const unsigned char *data, size_t len) {
  	size_t i, j;
  	
  	if (len == 0) return;
  	
  	for (i = 0; i < len; i++) {
  		if (i % 16 == 0) fprintf(stderr, "  ");
  		
  		fprintf(stderr, "%02x ", data[i]);
  		
  		if ((i + 1) % 16 == 0) {
  			fprintf(stderr, "  ");
  			for (j = 0; j <= i % 16; j++) {
  				unsigned char c;
  				
  				if (i-15+j >= len) break;
  				
  				c = data[i-15+j];
  				
  				fprintf(stderr, "%c", c > 32 && c < 128 ? c : '.');
  			}
  			
  			fprintf(stderr, "\n");
  		}
  	}
  	
  	if (len % 16 != 0) {
  		for (j = i % 16; j < 16; j++) {
  			fprintf(stderr, "   ");
  		}
  		
  		fprintf(stderr, "  ");
  		for (j = i & ~0xf; j < len; j++) {
  			unsigned char c;
  			
  			c = data[j];
  			fprintf(stderr, "%c", c > 32 && c < 128 ? c : '.');
  		}
  		fprintf(stderr, "\n");
  	}
  }
  #endif 
  
  static int connection_handle_read(server *srv, connection *con) {
  	int len;
  	buffer *b;
  	int toread;
  #ifdef USE_OPENSSL
  	server_socket *srv_sock = con->srv_socket;
  #endif
  
  	b = chunkqueue_get_append_buffer(con->read_queue);
  	buffer_prepare_copy(b, 4096);
  
  #ifdef USE_OPENSSL
  	if (srv_sock->is_ssl) {
  		len = SSL_read(con->ssl, b->ptr, b->size - 1);
  	} else {
  		if (ioctl(con->fd, FIONREAD, &toread)) {
  			log_error_write(srv, __FILE__, __LINE__, "sd", 
  					"unexpected end-of-file:",
  					con->fd);
  			return -1;
  		}
  		buffer_prepare_copy(b, toread);
  
  		len = read(con->fd, b->ptr, b->size - 1);
  	}
  #elif defined(__WIN32)
  	len = recv(con->fd, b->ptr, b->size - 1, 0);
  #else
  	if (ioctl(con->fd, FIONREAD, &toread)) {
  		log_error_write(srv, __FILE__, __LINE__, "sd", 
  				"unexpected end-of-file:",
  				con->fd);
  		return -1;
  	}
  	buffer_prepare_copy(b, toread);
  
  	len = read(con->fd, b->ptr, b->size - 1);
  #endif
  	
  	if (len < 0) {
  		con->is_readable = 0;
  		
  #ifdef USE_OPENSSL
  		if (srv_sock->is_ssl) {
  			int r, ssl_err;
  			
  			switch ((r = SSL_get_error(con->ssl, len))) {
  			case SSL_ERROR_WANT_READ:
  				return 0;
  			case SSL_ERROR_SYSCALL:
  				/**
  				 * man SSL_get_error()
  				 * 
  				 * SSL_ERROR_SYSCALL
  				 *   Some I/O error occurred.  The OpenSSL error queue may contain more 
  				 *   information on the error.  If the error queue is empty (i.e.
  				 *   ERR_get_error() returns 0), ret can be used to find out more about 
  				 *   the error: If ret == 0, an EOF was observed that violates the
  				 *   protocol.  If ret == -1, the underlying BIO reported an I/O error 
  				 *   (for socket I/O on Unix systems, consult errno for details).
  				 *
  				 */
  				while((ssl_err = ERR_get_error())) {
  					/* get all errors from the error-queue */
  					log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:", 
  							r, ERR_error_string(ssl_err, NULL));
  				}
  
  				switch(errno) {
  				default:
  					log_error_write(srv, __FILE__, __LINE__, "sddds", "SSL:", 
  							len, r, errno,
  							strerror(errno));
  					break;
  				}
  				
  				break;
  			case SSL_ERROR_ZERO_RETURN:
  				/* clean shutdown on the remote side */
  				
  				if (r == 0) {
  					/* FIXME: later */
  				}
  				
  				/* fall thourgh */
  			default:
  				while((ssl_err = ERR_get_error())) {
  					/* get all errors from the error-queue */
  					log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:", 
  							r, ERR_error_string(ssl_err, NULL));
  				}
  				break;
  			}
  		} else {
  			if (errno == EAGAIN) return 0;
  			if (errno == EINTR) {
  				/* we have been interrupted before we could read */
  				con->is_readable = 1;
  				return 0;
  			}
  		
  			if (errno != ECONNRESET) {
  				/* expected for keep-alive */
  				log_error_write(srv, __FILE__, __LINE__, "ssd", "connection closed - read failed: ", strerror(errno), errno);
  			}
  		}
  #else
  		if (errno == EAGAIN) return 0;
  		if (errno == EINTR) {
  			/* we have been interrupted before we could read */
  			con->is_readable = 1;
  			return 0;
  		}
  		
  		if (errno != ECONNRESET) {
  			/* expected for keep-alive */
  			log_error_write(srv, __FILE__, __LINE__, "ssd", "connection closed - read failed: ", strerror(errno), errno);
  		}
  #endif
  		connection_set_state(srv, con, CON_STATE_ERROR);
  		
  		return -1;
  	} else if (len == 0) {
  		con->is_readable = 0;
  		/* the other end close the connection -> KEEP-ALIVE */
  
  		/* pipelining */
  
  		return -2;
  	} else if ((size_t)len < b->size - 1) {
  		/* we got less then expected, wait for the next fd-event */
  		
  		con->is_readable = 0;
  	}
  	
  	b->used = len;
  	b->ptr[b->used++] = '\0';
  	
  	con->bytes_read += len;
  #if 0
  	dump_packet(b->ptr, len);
  #endif
  	
  	return 0;
  }
  
  static int connection_handle_write_prepare(server *srv, connection *con) {
  	if (con->mode == DIRECT) {
  		/* static files */
  		switch(con->request.http_method) {
  		case HTTP_METHOD_GET:
  		case HTTP_METHOD_POST:
  		case HTTP_METHOD_HEAD:
  		case HTTP_METHOD_PUT:
  		case HTTP_METHOD_MKCOL:
  		case HTTP_METHOD_DELETE:
  		case HTTP_METHOD_COPY:
  		case HTTP_METHOD_MOVE:
  		case HTTP_METHOD_PROPFIND:
  		case HTTP_METHOD_PROPPATCH:
  			break;
  		case HTTP_METHOD_OPTIONS:
  			/*
  			 * 400 is coming from the request-parser BEFORE uri.path is set
  			 * 403 is from the response handler when noone else catched it 
  			 * 
  			 * */
  			if (con->uri.path->used &&
  			    con->uri.path->ptr[0] != '*') {
  				response_header_insert(srv, con, CONST_STR_LEN("Allow"), CONST_STR_LEN("OPTIONS, GET, HEAD, POST"));
  
  				con->http_status = 200;
  				con->file_finished = 1;
  
  				chunkqueue_reset(con->write_queue);
  			}
  			break;
  		default:
  			switch(con->http_status) {
  			case 400: /* bad request */
  			case 505: /* unknown protocol */
  			case 207: /* this was webdav */
  				break;
  			default:
  				con->http_status = 501;
  				break;
  			}
  			break;
  		}
  	}
  	
  	if (con->http_status == 0) {
  		con->http_status = 403;
  	}
  	
  	switch(con->http_status) {
  	case 400: /* class: header + custom body */
  	case 401:
  	case 403:
  	case 404:
  	case 408:
  	case 411:
  	case 416:
  	case 500:
  	case 501:
  	case 503:
  	case 505: 
  		if (con->mode != DIRECT) break;
  		
  		con->file_finished = 0;
  		
  		buffer_reset(con->physical.path);
  				
  		/* try to send static errorfile */
  		if (!buffer_is_empty(con->conf.errorfile_prefix)) {
  			stat_cache_entry *sce = NULL;
  			
  			buffer_copy_string_buffer(con->physical.path, con->conf.errorfile_prefix);
  			buffer_append_string(con->physical.path, get_http_status_body_name(con->http_status));
  			
  			if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
  				con->file_finished = 1;
  				
  				http_chunk_append_file(srv, con, con->physical.path, 0, sce->st.st_size);
  				response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type));
  			}
  		}
  		
  		if (!con->file_finished) {			
  			buffer *b;
  			
  			buffer_reset(con->physical.path);
  			
  			con->file_finished = 1;
  			b = chunkqueue_get_append_buffer(con->write_queue);
  				
  			/* build default error-page */
  			buffer_copy_string(b, 
  					   "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
  					   "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
  					   "         \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
  					   "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
  					   " <head>\n"
  					   "  <title>");
  			buffer_append_long(b, con->http_status);
  			buffer_append_string(b, " - ");
  			buffer_append_string(b, get_http_status_name(con->http_status));
  			
  			buffer_append_string(b,
  					     "</title>\n"
  					     " </head>\n"
  					     " <body>\n"
  					     "  <h1>");
  			buffer_append_long(b, con->http_status);
  			buffer_append_string(b, " - ");
  			buffer_append_string(b, get_http_status_name(con->http_status));
  			
  			buffer_append_string(b,"</h1>\n" 
  					     " </body>\n"
  					     "</html>\n"
  					     );
  			
  			response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
  		}
  		/* fall through */
  	case 207:
  	case 200: /* class: header + body */
  	case 302:
  		break;
  		
  	case 206: /* write_queue is already prepared */
  		con->file_finished = 1;
  		
  		break;
  	case 205: /* class: header only */
  	case 301:
  	case 304:
  	default:
  		/* disable chunked encoding again as we have no body */
  		con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED;
  		chunkqueue_reset(con->write_queue);
  		
  		con->file_finished = 1;
  		break;
  	}
  	
  
  	if (con->file_finished) {
  		/* we have all the content and chunked encoding is not used, set a content-length */ 
  		
  		if ((!(con->parsed_response & HTTP_CONTENT_LENGTH)) && 
  		    (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0) {
  			buffer_copy_off_t(srv->tmp_buf, chunkqueue_length(con->write_queue));
  		
  			response_header_overwrite(srv, con, CONST_STR_LEN("Content-Length"), CONST_BUF_LEN(srv->tmp_buf));
  		}
  	} else {
  		/* disable keep-alive if size-info for the body is missing */
  		if ((con->parsed_response & HTTP_CONTENT_LENGTH) &&
  		    ((con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0)) {
  			con->keep_alive = 0;
  		}
  		
  		if (0 == (con->parsed_response & HTTP_CONNECTION)) {
  			/* (f)cgi did'nt send Connection: header
  			 *                          
  			 * shall we ?
  			 */
  			if (((con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0) &&
  			    (con->parsed_response & HTTP_CONTENT_LENGTH) == 0) {
  				/* without content_length, no keep-alive */
  				
  				con->keep_alive = 0;
  			}
  		} else {
  			/* a subrequest disable keep-alive although the client wanted it */
  			if (con->keep_alive && !con->response.keep_alive) {
  				con->keep_alive = 0;
  				
  				/* FIXME: we have to drop the Connection: Header from the subrequest */
  			}
  		}
  	}
  	
  	if (con->request.http_method == HTTP_METHOD_HEAD) {
  		chunkqueue_reset(con->write_queue);
  	}
  
  	http_response_write_header(srv, con);
  		
  	return 0;
  }
  
  static int connection_handle_write(server *srv, connection *con) {
  	switch(network_write_chunkqueue(srv, con, con->write_queue)) {
  	case 0:
  		if (con->file_finished) {
  			connection_set_state(srv, con, CON_STATE_RESPONSE_END);
  			joblist_append(srv, con);
  		}
  		break;
  	case -1: /* error on our side */
  		log_error_write(srv, __FILE__, __LINE__, "sd",
  				"connection closed: write failed on fd", con->fd);
  		connection_set_state(srv, con, CON_STATE_ERROR);
  		joblist_append(srv, con);
  		break;
  	case -2: /* remote close */
  		connection_set_state(srv, con, CON_STATE_ERROR);
  		joblist_append(srv, con);
  		break;
  	case 1:
  		con->is_writable = 0;
  		
  		/* not finished yet -> WRITE */
  		break;
  	}
  	
  	return 0;
  }
  
  
  
  connection *connection_init(server *srv) {
  	connection *con;
  	
  	UNUSED(srv);
  
  	con = calloc(1, sizeof(*con));
  		
  	con->fd = 0;
  	con->ndx = -1;
  	con->fde_ndx = -1;
  	con->bytes_written = 0;
  	con->bytes_read = 0;
  	con->bytes_header = 0;
  	con->loops_per_request = 0;
  
  #define CLEAN(x) \
  	con->x = buffer_init();
  	
  	CLEAN(request.uri);
  	CLEAN(request.request_line);
  	CLEAN(request.request);
  	CLEAN(request.pathinfo);
  	
  	CLEAN(request.orig_uri);
  	
  	CLEAN(uri.scheme);
  	CLEAN(uri.authority);
  	CLEAN(uri.path);
  	CLEAN(uri.path_raw);
  	CLEAN(uri.query);
  	
  	CLEAN(physical.doc_root);
  	CLEAN(physical.path);
  	CLEAN(physical.basedir);
  	CLEAN(physical.rel_path);
  	CLEAN(physical.etag);
  	CLEAN(parse_request);
  	
  	CLEAN(authed_user);
  	CLEAN(server_name);
  	CLEAN(error_handler);
  	CLEAN(dst_addr_buf);
  	
  #undef CLEAN
  	con->write_queue = chunkqueue_init();
  	con->read_queue = chunkqueue_init();
  	con->request_content_queue = chunkqueue_init();
  	chunkqueue_set_tempdirs(con->request_content_queue, srv->srvconf.upload_tempdirs);
  
  	con->request.headers      = array_init();
  	con->response.headers     = array_init();
  	con->environment     = array_init();
  	
  	/* init plugin specific connection structures */
  	
  	con->plugin_ctx = calloc(1, (srv->plugins.used + 1) * sizeof(void *));
  	
  	con->cond_cache = calloc(srv->config_context->used, sizeof(cond_cache_t));
  	config_setup_connection(srv, con);
  	
  	return con;
  }
  
  void connections_free(server *srv) {
  	connections *conns = srv->conns;
  	size_t i;	
  	
  	for (i = 0; i < conns->size; i++) {
  		connection *con = conns->ptr[i];
  		
  		connection_reset(srv, con);
  		
  		chunkqueue_free(con->write_queue);
  		chunkqueue_free(con->read_queue);
  		chunkqueue_free(con->request_content_queue);
  		array_free(con->request.headers);
  		array_free(con->response.headers);
  		array_free(con->environment);
  
  #define CLEAN(x) \
  	buffer_free(con->x);
  		
  		CLEAN(request.uri);
  		CLEAN(request.request_line);
  		CLEAN(request.request);
  		CLEAN(request.pathinfo);
  		
  		CLEAN(request.orig_uri);
  		
  		CLEAN(uri.scheme);
  		CLEAN(uri.authority);
  		CLEAN(uri.path);
  		CLEAN(uri.path_raw);
  		CLEAN(uri.query);
  		
  		CLEAN(physical.doc_root);
  		CLEAN(physical.path);
  		CLEAN(physical.basedir);
  		CLEAN(physical.etag);
  		CLEAN(physical.rel_path);
  		CLEAN(parse_request);
  		
  		CLEAN(authed_user);
  		CLEAN(server_name);
  		CLEAN(error_handler);
  		CLEAN(dst_addr_buf);
  #undef CLEAN
  		free(con->plugin_ctx);
  		free(con->cond_cache);
  		
  		free(con);
  	}
  	
  	free(conns->ptr);
  }
  
  
  int connection_reset(server *srv, connection *con) {
  	size_t i;
  	
  	plugins_call_connection_reset(srv, con);
  	
  	con->is_readable = 1;
  	con->is_writable = 1;
  	con->http_status = 0;
  	con->file_finished = 0;
  	con->file_started = 0;
  	con->got_response = 0;
  	
  	con->parsed_response = 0;
  	
  	con->bytes_written = 0;
  	con->bytes_written_cur_second = 0;
  	con->bytes_read = 0;
  	con->bytes_header = 0;
  	con->loops_per_request = 0;
  	
  	con->request.http_method = HTTP_METHOD_UNSET;
  	con->request.http_version = HTTP_VERSION_UNSET;
  	
  	con->request.http_if_modified_since = NULL;
  	con->request.http_if_none_match = NULL;
  	
  	con->response.keep_alive = 0;
  	con->response.content_length = -1;
  	con->response.transfer_encoding = 0;
  	
  	con->mode = DIRECT;
  	
  #define CLEAN(x) \
  	if (con->x) buffer_reset(con->x);
  	
  	CLEAN(request.uri);
  	CLEAN(request.request_line);
  	CLEAN(request.pathinfo);
  	CLEAN(request.request);
  	
  	CLEAN(request.orig_uri);
  	
  	CLEAN(uri.scheme);
  	CLEAN(uri.authority);
  	CLEAN(uri.path);
  	CLEAN(uri.path_raw);
  	CLEAN(uri.query);
  	
  	CLEAN(physical.doc_root);
  	CLEAN(physical.path);
  	CLEAN(physical.basedir);
  	CLEAN(physical.rel_path);
  	CLEAN(physical.etag);
  	
  	CLEAN(parse_request);
  	
  	CLEAN(authed_user);
  	CLEAN(server_name);
  	CLEAN(error_handler);
  #undef CLEAN	
  	
  #define CLEAN(x) \
  	if (con->x) con->x->used = 0;	
  	
  #undef CLEAN
  	
  #define CLEAN(x) \
  		con->request.x = NULL;
  	
  	CLEAN(http_host);
  	CLEAN(http_range);
  	CLEAN(http_content_type);
  #undef CLEAN
  	con->request.content_length = 0;
  	
  	array_reset(con->request.headers);
  	array_reset(con->response.headers);
  	array_reset(con->environment);
  	
  	chunkqueue_reset(con->write_queue);
  	chunkqueue_reset(con->request_content_queue);
  
  	/* the plugins should cleanup themself */	
  	for (i = 0; i < srv->plugins.used; i++) {
  		plugin *p = ((plugin **)(srv->plugins.ptr))[i];
  		plugin_data *pd = p->data;
  
  		if (!pd) continue;
  
  		if (con->plugin_ctx[pd->id] != NULL) {
  			log_error_write(srv, __FILE__, __LINE__, "sb", "missing cleanup in", p->name);
  		}
  
  		con->plugin_ctx[pd->id] = NULL;
  	}
  	
  #if COND_RESULT_UNSET
  	for (i = srv->config_context->used - 1; i >= 0; i --) {
  		con->cond_cache[i].result = COND_RESULT_UNSET;
  		con->cond_cache[i].patterncount = 0;
  	}
  #else
  	memset(con->cond_cache, 0, sizeof(cond_cache_t) * srv->config_context->used);
  #endif
  	
  	con->header_len = 0;
  	con->in_error_handler = 0;
  	
  	config_setup_connection(srv, con);
  	
  	return 0;
  }
  
  /**
   * 
   * search for \r\n\r\n 
   * 
   * this is a special 32bit version which is using a sliding window for
   * the comparisions 
   * 
   * how it works:
   * 
   * b:      'abcdefg'
   * rnrn:   'cdef'
   * 
   * cmpbuf: abcd != cdef
   * cmpbuf: bcde != cdef
   * cmpbuf: cdef == cdef -> return &c
   * 
   * cmpbuf and rnrn are treated as 32bit uint and bit-ops are used to 
   * maintain cmpbuf and rnrn
   * 
   */
  
  char *buffer_search_rnrn(buffer *b) {
  	uint32_t cmpbuf, rnrn;
  	char *cp;
  	size_t i;
  	
  	if (b->used < 4) return NULL;
  	
  	rnrn = ('\r' << 24) | ('\n' << 16) |
  		('\r' << 8) | ('\n' << 0);
  	
  	cmpbuf = (b->ptr[0] << 24) | (b->ptr[1] << 16) |
  		(b->ptr[2] << 8) | (b->ptr[3] << 0);
  		
  	cp = b->ptr + 4;
  	for (i = 0; i < b->used - 4; i++) {
  		if (cmpbuf == rnrn) return cp - 4;
  			
  		cmpbuf = (cmpbuf << 8 | *(cp++)) & 0xffffffff;
  	}
  	
  	return NULL;
  }
  /**
   * handle all header and content read
   *
   * we get called by the state-engine and by the fdevent-handler
   */
  int connection_handle_read_state(server *srv, connection *con)  {
  	int ostate = con->state;
  	char *h_term = NULL;
  	chunk *c;
  	chunkqueue *cq = con->read_queue;
  	chunkqueue *dst_cq = con->request_content_queue;
  	
  	if (con->is_readable) {
  		con->read_idle_ts = srv->cur_ts;
  	
  		switch(connection_handle_read(srv, con)) {
  		case -1:
  			return -1;
  		case -2:
  			/* remote side closed the connection
  			 * if we still have content, handle it, if not leave here */
  
  			if (cq->first == cq->last &&
  			    cq->first->mem->used == 0) {
  
  				/* conn-closed, leave here */
  				connection_set_state(srv, con, CON_STATE_ERROR);
  			}
  		default:
  			break;
  		}
  	}
  
  	/* the last chunk might be empty */
  	for (c = cq->first; c;) {
  		if (cq->first == c && c->mem->used == 0) {
  			/* the first node is empty */
  			/* ... and it is empty, move it to unused */
  
  			cq->first = c->next;
  			if (cq->first == NULL) cq->last = NULL;
  
  			c->next = cq->unused;
  			cq->unused = c;
  			cq->unused_chunks++;
  
  			c = cq->first;
  		} else if (c->next && c->next->mem->used == 0) {
  			chunk *fc;
  			/* next node is the last one */
  			/* ... and it is empty, move it to unused */
  
  			fc = c->next;
  			c->next = fc->next;
  
  			fc->next = cq->unused;
  			cq->unused = fc;
  			cq->unused_chunks++;
  
  			/* the last node was empty */
  			if (c->next == NULL) {
  				cq->last = c;
  			} 
  
  			c = c->next;
  		} else {
  			c = c->next;
  		}
  	}
  	
  	/* nothing to handle */
  	if (cq->first == NULL) return 0;
  
  	switch(ostate) {
  	case CON_STATE_READ:
  		/* prepare con->request.request */
  		c = cq->first;
  		
  		/* check if we need the full package */
  		if (con->request.request->used == 0) {
  			buffer b;
  			
  			b.ptr = c->mem->ptr + c->offset;
  			b.used = c->mem->used - c->offset;
  			
  			if (NULL != (h_term = buffer_search_rnrn(&b))) {
  				/* \r\n\r\n found
  				 * - copy everything incl. the terminator to request.request
  				 */
  				
  				buffer_copy_string_len(con->request.request, 
  						       b.ptr, 
  						       h_term - b.ptr + 4);
  				
  				/* the buffer has been read up to the terminator */
  				c->offset += h_term - b.ptr + 4;
  			} else {
  				/* not found, copy everything */
  				buffer_copy_string_len(con->request.request, c->mem->ptr + c->offset, c->mem->used - c->offset - 1);
  				c->offset = c->mem->used - 1;
  			}
  		} else {
  			/* have to take care of overlapping header terminators */
  			
  			size_t l = con->request.request->used - 2;
  			char *s  = con->request.request->ptr;
  			buffer b;
  			
  			b.ptr = c->mem->ptr + c->offset;
  			b.used = c->mem->used - c->offset;
  			
  			if (con->request.request->used - 1 > 3 &&
  			    c->mem->used > 1 &&
  			    s[l-2] == '\r' &&
  			    s[l-1] == '\n' &&
  			    s[l-0] == '\r' &&
  			    c->mem->ptr[0] == '\n') {
  				buffer_append_string_len(con->request.request, c->mem->ptr + c->offset, 1);
  				c->offset += 1;
  				
  				h_term = con->request.request->ptr;
  			} else if (con->request.request->used - 1 > 2 &&
  				   c->mem->used > 2 &&
  				   s[l-1] == '\r' &&
  				   s[l-0] == '\n' &&
  				   c->mem->ptr[0] == '\r' &&
  				   c->mem->ptr[1] == '\n') {
  				buffer_append_string_len(con->request.request, c->mem->ptr + c->offset, 2);
  				c->offset += 2;
  				
  				h_term = con->request.request->ptr;
  			} else if (con->request.request->used - 1 > 1 &&
  				   c->mem->used > 3 &&
  				   s[l-0] == '\r' &&
  				   c->mem->ptr[0] == '\n' &&
  				   c->mem->ptr[1] == '\r' &&
  				   c->mem->ptr[2] == '\n') {
  				buffer_append_string_len(con->request.request, c->mem->ptr + c->offset, 3);
  				c->offset += 3;
  				
  				h_term = con->request.request->ptr;
  			} else if (NULL != (h_term = buffer_search_string_len(&b, "\r\n\r\n", 4))) {
  				/* \r\n\r\n found
  				 * - copy everything incl. the terminator to request.request
  				 */
  				
  				buffer_append_string_len(con->request.request, 
  						       c->mem->ptr + c->offset, 
  						       c->offset + h_term - b.ptr + 4);
  				
  				/* the buffer has been read up to the terminator */
  				c->offset += h_term - b.ptr + 4;
  			} else {
  				/* not found, copy everything */
  				buffer_append_string_len(con->request.request, c->mem->ptr + c->offset, c->mem->used - c->offset - 1);
  				c->offset = c->mem->used - 1;
  			}
  		}
  
  		/* con->request.request is setup up */
  		if (h_term) {
  			connection_set_state(srv, con, CON_STATE_REQUEST_END);
  		} else if (con->request.request->used > 64 * 1024) {
  			log_error_write(srv, __FILE__, __LINE__, "sd", "http-header larger then 64k -> disconnected", chunkqueue_length(cq));
  
  			con->http_status = 414; /* Request-URI too large */
  			con->keep_alive = 0;
  			connection_set_state(srv, con, CON_STATE_REQUEST_END);
  		}
  		break;
  	case CON_STATE_READ_POST: 
  		for (c = cq->first; c && (dst_cq->bytes_in != (off_t)con->request.content_length); c = c->next) {
  			off_t weWant, weHave, toRead;
  			
  			weWant = con->request.content_length - dst_cq->bytes_in;
  			
  			assert(c->mem->used);
  			
  			weHave = c->mem->used - c->offset - 1;
  				
  			toRead = weHave > weWant ? weWant : weHave;
  
  			/* the new way, copy everything into a chunkqueue whcih might use tempfiles */
  			if (con->request.content_length > 64 * 1024) {
  				chunk *dst_c = NULL;
  				/* copy everything to max 1Mb sized tempfiles */
  
  				/*
  				 * if the last chunk is 
  				 * - smaller than 1Mb (size < 1Mb)
  				 * - not read yet (offset == 0)
  				 * -> append to it
  				 * otherwise
  				 * -> create a new chunk 
  				 * 
  				 * */
  
  				if (dst_cq->last &&
  				    dst_cq->last->type == FILE_CHUNK &&
  				    dst_cq->last->file.is_temp &&
  				    dst_cq->last->offset == 0) {
  					/* ok, take the last chunk for our job */
  
  			 		if (dst_cq->last->file.length < 1 * 1024 * 1024) {
  						dst_c = dst_cq->last;
  
  						if (dst_c->file.fd == -1) {
  							/* this should not happen as we cache the fd, but you never know */
  							dst_c->file.fd = open(dst_c->file.name->ptr, O_WRONLY | O_APPEND);
  						}
  					} else {
  						/* the chunk is too large now, close it */
  						dst_c = dst_cq->last;
  
  						if (dst_c->file.fd != -1) {
  							close(dst_c->file.fd);
  							dst_c->file.fd = -1;
  						}
  						dst_c = chunkqueue_get_append_tempfile(dst_cq);
  					}
  				} else {
  					dst_c = chunkqueue_get_append_tempfile(dst_cq);
  				}
  
  				/* we have a chunk, let's write to it */
  
  				if (dst_c->file.fd == -1) {
  					/* we don't have file to write to, 
  					 * EACCES might be one reason.
  					 *
  					 * Instead of sending 500 we send 413 and say the request is too large
  					 *  */
  
  					log_error_write(srv, __FILE__, __LINE__, "sbs",
  							"denying upload as opening to temp-file for upload failed:", 
  							dst_c->file.name, strerror(errno));
  
  					con->http_status = 413; /* Request-Entity too large */
  					con->keep_alive = 0;
  					connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
  
  					break;
  				}
  
  				if (toRead != write(dst_c->file.fd, c->mem->ptr + c->offset, toRead)) {
  					/* write failed for some reason ... disk full ? */ 
  					log_error_write(srv, __FILE__, __LINE__, "sbs",
  							"denying upload as writing to file failed:", 
  							dst_c->file.name, strerror(errno));
  					
  					con->http_status = 413; /* Request-Entity too large */
  					con->keep_alive = 0;
  					connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
  				
  					close(dst_c->file.fd);
  					dst_c->file.fd = -1;
  
  					break;
  				}
  
  				dst_c->file.length += toRead;
  					
  				if (dst_cq->bytes_in + toRead == (off_t)con->request.content_length) {
  					/* we read everything, close the chunk */
  					close(dst_c->file.fd);
  					dst_c->file.fd = -1;
  				}
  			} else {
  				buffer *b;
  
  				b = chunkqueue_get_append_buffer(dst_cq);
  				buffer_copy_string_len(b, c->mem->ptr + c->offset, toRead);
  			}
  			
  			c->offset += toRead;
  			dst_cq->bytes_in += toRead;
  		}
  
  		/* Content is ready */
  		if (dst_cq->bytes_in == (off_t)con->request.content_length) {
  			connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
  		}
  			
  		break;
  	}
  
  	chunkqueue_remove_finished_chunks(cq);
  
  	return 0;
  }
  
  handler_t connection_handle_fdevent(void *s, void *context, int revents) {
  	server     *srv = (server *)s;
  	connection *con = context;
  	
  	joblist_append(srv, con);
  	
  	if (revents & FDEVENT_IN) {
  		con->is_readable = 1;
  #if 0
  		log_error_write(srv, __FILE__, __LINE__, "sd", "read-wait - done", con->fd);
  #endif
  	}
  	if (revents & FDEVENT_OUT) {
  		con->is_writable = 1;
  		/* we don't need the event twice */
  	}
  	
  	
  	if (revents & ~(FDEVENT_IN | FDEVENT_OUT)) {
  		/* looks like an error */
  						
  		/* FIXME: revents = 0x19 still means that we should read from the queue */
  		if (revents & FDEVENT_HUP) {
  			if (con->state == CON_STATE_CLOSE) {
  				con->close_timeout_ts = 0;
  			} else {
  				/* sigio reports the wrong event here
  				 * 
  				 * there was no HUP at all 
  				 */
  #ifdef USE_LINUX_SIGIO
  				if (srv->ev->in_sigio == 1) {
  					log_error_write(srv, __FILE__, __LINE__, "sd",
  						"connection closed: poll() -> HUP", con->fd);
  				} else {
  					connection_set_state(srv, con, CON_STATE_ERROR);
  				}
  #else
  				connection_set_state(srv, con, CON_STATE_ERROR);
  #endif
  				
  			}
  		} else if (revents & FDEVENT_ERR) {
  #ifndef USE_LINUX_SIGIO
  			log_error_write(srv, __FILE__, __LINE__, "sd",
  					"connection closed: poll() -> ERR", con->fd);
  #endif	
  			connection_set_state(srv, con, CON_STATE_ERROR);
  		} else {
  			log_error_write(srv, __FILE__, __LINE__, "sd",
  					"connection closed: poll() -> ???", revents);
  		} 
  	}
  	
  	if (con->state == CON_STATE_READ ||
  	    con->state == CON_STATE_READ_POST) {
  		connection_handle_read_state(srv, con);
  	}
  	
  	if (con->state == CON_STATE_WRITE &&
  	    !chunkqueue_is_empty(con->write_queue) &&
  	    con->is_writable) {
  		
  		if (-1 == connection_handle_write(srv, con)) {
  			connection_set_state(srv, con, CON_STATE_ERROR);
  			
  			log_error_write(srv, __FILE__, __LINE__, "ds",
  					con->fd,
  					"handle write failed.");
  		} else if (con->state == CON_STATE_WRITE) {
  			con->write_request_ts = srv->cur_ts;
  		}
  	}
  	
  	if (con->state == CON_STATE_CLOSE) {
  		/* flush the read buffers */
  		int b;
  		
  		if (ioctl(con->fd, FIONREAD, &b)) {
  			log_error_write(srv, __FILE__, __LINE__, "ss",
  					"ioctl() failed", strerror(errno));
  		}
  		
  		if (b > 0) {
  			char buf[1024];
  			log_error_write(srv, __FILE__, __LINE__, "sdd",
  					"CLOSE-read()", con->fd, b);
  			
  			/* */
  			read(con->fd, buf, sizeof(buf));
  		} else {
  			/* nothing to read */
  			
  			con->close_timeout_ts = 0;
  		}
  	}
  	
  	return HANDLER_FINISHED;
  }
  
  
  connection *connection_accept(server *srv, server_socket *srv_socket) {
  	/* accept everything */
  
  	/* search an empty place */
  	int cnt;
  	sock_addr cnt_addr;
  	socklen_t cnt_len;
  	/* accept it and register the fd */
  	
  	cnt_len = sizeof(cnt_addr);
  
  	if (-1 == (cnt = accept(srv_socket->fd, (struct sockaddr *) &cnt_addr, &cnt_len))) {
  		if ((errno != EAGAIN) &&
  		    (errno != EINTR)) {
  			log_error_write(srv, __FILE__, __LINE__, "ssd", "accept failed:", strerror(errno), errno);
  		}
  		return NULL;
  	} else {
  		connection *con;
  		
  		srv->cur_fds++;
  		
  		/* ok, we have the connection, register it */
  #if 0
  		log_error_write(srv, __FILE__, __LINE__, "sd",
  				"appected()", cnt);
  #endif
  		srv->con_opened++;
  		
  		con = connections_get_new_connection(srv);
  		
  		con->fd = cnt;
  		con->fde_ndx = -1;
  #if 0		
  		gettimeofday(&(con->start_tv), NULL);
  #endif		
  		fdevent_register(srv->ev, con->fd, connection_handle_fdevent, con);
  		
  		connection_set_state(srv, con, CON_STATE_REQUEST_START);
  		
  		con->connection_start = srv->cur_ts;
  		con->dst_addr = cnt_addr;
  		buffer_copy_string(con->dst_addr_buf, inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
  		con->srv_socket = srv_socket;
  		
  		if (-1 == (fdevent_fcntl_set(srv->ev, con->fd))) {
  			log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
  			return NULL;
  		}
  #ifdef USE_OPENSSL
  		/* connect FD to SSL */
  		if (srv_socket->is_ssl) {
  			if (NULL == (con->ssl = SSL_new(srv_socket->ssl_ctx))) {
  				log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:", 
  						ERR_error_string(ERR_get_error(), NULL));
  				
  				return NULL;
  			}
  			
  			SSL_set_accept_state(con->ssl);
  			con->conf.is_ssl=1;
  			
  			if (1 != (SSL_set_fd(con->ssl, cnt))) {
  				log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:", 
  						ERR_error_string(ERR_get_error(), NULL));
  				return NULL;
  			}
  		}
  #endif
  		return con;
  	}
  }
  
  
  int connection_state_machine(server *srv, connection *con) {
  	int done = 0, r;
  #ifdef USE_OPENSSL
  	server_socket *srv_sock = con->srv_socket;
  #endif
  	
  	if (srv->srvconf.log_state_handling) {
  		log_error_write(srv, __FILE__, __LINE__, "sds", 
  				"state at start", 
  				con->fd,
  				connection_get_state(con->state));
  	}
  
  	while (done == 0) {
  		size_t ostate = con->state;
  		int b;
  		
  		switch (con->state) {
  		case CON_STATE_REQUEST_START: /* transient */
  			if (srv->srvconf.log_state_handling) {
  				log_error_write(srv, __FILE__, __LINE__, "sds", 
  						"state for fd", con->fd, connection_get_state(con->state));
  			}
  			
  			con->request_start = srv->cur_ts;
  			con->read_idle_ts = srv->cur_ts;
  			
  			con->request_count++;
  			con->loops_per_request = 0;
  			
  			connection_set_state(srv, con, CON_STATE_READ);
  			
  			break;
  		case CON_STATE_REQUEST_END: /* transient */
  			if (srv->srvconf.log_state_handling) {
  				log_error_write(srv, __FILE__, __LINE__, "sds", 
  						"state for fd", con->fd, connection_get_state(con->state));
  			}
  			
  			if (http_request_parse(srv, con)) {
  				/* we have to read some data from the POST request */
  				
  				connection_set_state(srv, con, CON_STATE_READ_POST);
  
  				break;
  			}
  			
  			connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
  			
  			break;
  		case CON_STATE_HANDLE_REQUEST:
  			/* 
  			 * the request is parsed
  			 * 
  			 * decided what to do with the request
  			 * - 
  			 * 
  			 * 
  			 */
  			
  			if (srv->srvconf.log_state_handling) {
  				log_error_write(srv, __FILE__, __LINE__, "sds", 
  						"state for fd", con->fd, connection_get_state(con->state));
  			}
  			
  			switch (r = http_response_prepare(srv, con)) {
  			case HANDLER_FINISHED:
  				if (con->http_status == 404 ||
  				    con->http_status == 403) {
  					/* 404 error-handler */
  					
  					if (con->in_error_handler == 0 && 
  					    (!buffer_is_empty(con->conf.error_handler) ||
  					     !buffer_is_empty(con->error_handler))) {
  						/* call error-handler */
  						
  						con->error_handler_saved_status = con->http_status;
  						con->http_status = 0;
  						
  						if (buffer_is_empty(con->error_handler)) {
  							buffer_copy_string_buffer(con->request.uri, con->conf.error_handler);
  						} else {
  							buffer_copy_string_buffer(con->request.uri, con->error_handler);
  						}
  						buffer_reset(con->physical.path);
  						
  						con->in_error_handler = 1;
  						
  						connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
  						
  						done = -1;
  						break;
  					} else if (con->in_error_handler) {
  						/* error-handler is a 404 */
  						
  						/* continue as normal, status is the same */
  						log_error_write(srv, __FILE__, __LINE__, "sb", 
  								"Warning: Either the error-handler returned status 404 or the error-handler itself was not found:", con->request.uri);
  						log_error_write(srv, __FILE__, __LINE__, "sd", 
  								"returning the original status", con->error_handler_saved_status);
  						log_error_write(srv, __FILE__, __LINE__, "s", 
  								"If this is a rails app: check your production.log");
  						con->http_status = con->error_handler_saved_status;
  					}
  				} else if (con->in_error_handler) {
  					/* error-handler is back and has generated content */
  					/* if Status: was set, take it otherwise use 200 */
  				}
  				
  				if (con->http_status == 0) con->http_status = 200;
  				
  				/* we have something to send, go on */
  				connection_set_state(srv, con, CON_STATE_RESPONSE_START);
  				break;
  			case HANDLER_WAIT_FOR_FD:
  				srv->want_fds++;
  				
  				fdwaitqueue_append(srv, con);
  				
  				connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
  				
  				break;
  			case HANDLER_COMEBACK:
  				done = -1;
  			case HANDLER_WAIT_FOR_EVENT:
  				/* come back here */
  				connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
  				
  				break;
  			case HANDLER_ERROR:
  				/* something went wrong */
  				connection_set_state(srv, con, CON_STATE_ERROR);
  				break;
  			default:
  				log_error_write(srv, __FILE__, __LINE__, "sdd", "unknown ret-value: ", con->fd, r);
  				break;
  			}
  			
  			break;
  		case CON_STATE_RESPONSE_START:
  			/* 
  			 * the decision is done
  			 * - create the HTTP-Response-Header
  			 * 
  			 */
  			
  			if (srv->srvconf.log_state_handling) {
  				log_error_write(srv, __FILE__, __LINE__, "sds", 
  						"state for fd", con->fd, connection_get_state(con->state));
  			}
  			
  			if (-1 == connection_handle_write_prepare(srv, con)) {
  				connection_set_state(srv, con, CON_STATE_ERROR);
  				
  				break;
  			}
  			
  			connection_set_state(srv, con, CON_STATE_WRITE);
  			break;
  		case CON_STATE_RESPONSE_END: /* transient */
  			/* log the request */
  			
  			if (srv->srvconf.log_state_handling) {
  				log_error_write(srv, __FILE__, __LINE__, "sds", 
  						"state for fd", con->fd, connection_get_state(con->state));
  			}
  			
  			plugins_call_handle_request_done(srv, con);
  			
  			srv->con_written++;
  			
  			if (con->keep_alive) {
  				connection_set_state(srv, con, CON_STATE_REQUEST_START);
  				
  #if 0					
  				con->request_start = srv->cur_ts;
  				con->read_idle_ts = srv->cur_ts;
  #endif
  			} else {
  				switch(r = plugins_call_handle_connection_close(srv, con)) {
  				case HANDLER_GO_ON:
  				case HANDLER_FINISHED:
  					break;
  				default:
  					log_error_write(srv, __FILE__, __LINE__, "sd", "unhandling return value", r);
  					break;
  				}
  				
  #ifdef USE_OPENSSL
  				if (srv_sock->is_ssl) {
  					switch (SSL_shutdown(con->ssl)) {
  					case 1:
  						/* done */
  						break;
  					case 0:
  						/* wait for fd-event 
  						 * 
  						 * FIXME: wait for fdevent and call SSL_shutdown again
  						 * 
  						 */
  						
  						break;
  					default:
  						log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:", 
  								ERR_error_string(ERR_get_error(), NULL));
  					}
  				}
  #endif
  				connection_close(srv, con);
  				
  				srv->con_closed++;
  			}
  			
  			connection_reset(srv, con);
  			
  			break;
  		case CON_STATE_CONNECT:
  			if (srv->srvconf.log_state_handling) {
  				log_error_write(srv, __FILE__, __LINE__, "sds", 
  						"state for fd", con->fd, connection_get_state(con->state));
  			}
  			
  			chunkqueue_reset(con->read_queue);
  			
  			con->request_count = 0;
  			
  			break;
  		case CON_STATE_CLOSE:
  			if (srv->srvconf.log_state_handling) {
  				log_error_write(srv, __FILE__, __LINE__, "sds", 
  						"state for fd", con->fd, connection_get_state(con->state));
  			}
  			
  			if (con->keep_alive) {
  				if (ioctl(con->fd, FIONREAD, &b)) {
  					log_error_write(srv, __FILE__, __LINE__, "ss",
  							"ioctl() failed", strerror(errno));
  				}
  				if (b > 0) {
  					char buf[1024];
  					log_error_write(srv, __FILE__, __LINE__, "sdd",
  							"CLOSE-read()", con->fd, b);
  					
  					/* */
  					read(con->fd, buf, sizeof(buf));
  				} else {
  					/* nothing to read */
  					
  					con->close_timeout_ts = 0;
  				}
  			} else {
  				con->close_timeout_ts = 0;
  			}
  			
  			if (srv->cur_ts - con->close_timeout_ts > 1) {
  				connection_close(srv, con);
  				
  				if (srv->srvconf.log_state_handling) {
  					log_error_write(srv, __FILE__, __LINE__, "sd", 
  							"connection closed for fd", con->fd);
  				}
  			}
  			
  			break;
  		case CON_STATE_READ_POST:
  		case CON_STATE_READ:
  			if (srv->srvconf.log_state_handling) {
  				log_error_write(srv, __FILE__, __LINE__, "sds", 
  						"state for fd", con->fd, connection_get_state(con->state));
  			}
  			
  			connection_handle_read_state(srv, con);
  			break;
  		case CON_STATE_WRITE:
  			if (srv->srvconf.log_state_handling) {
  				log_error_write(srv, __FILE__, __LINE__, "sds", 
  						"state for fd", con->fd, connection_get_state(con->state));
  			}
  			
  			/* only try to write if we have something in the queue */
  			if (!chunkqueue_is_empty(con->write_queue)) {
  #if 0
  				log_error_write(srv, __FILE__, __LINE__, "dsd",
  						con->fd,
  						"packets to write:",
  						con->write_queue->used);
  #endif
  			}
  			if (!chunkqueue_is_empty(con->write_queue) && con->is_writable) {
  				if (-1 == connection_handle_write(srv, con)) {
  					log_error_write(srv, __FILE__, __LINE__, "ds",
  							con->fd,
  							"handle write failed.");
  					connection_set_state(srv, con, CON_STATE_ERROR);
  				} else if (con->state == CON_STATE_WRITE) {
  					con->write_request_ts = srv->cur_ts;
  				}
  			}
  			
  			break;
  		case CON_STATE_ERROR: /* transient */
  			
  			/* even if the connection was drop we still have to write it to the access log */
  			if (con->http_status) {
  				plugins_call_handle_request_done(srv, con);
  			}
  #ifdef USE_OPENSSL
  			if (srv_sock->is_ssl) {
  				int ret;
  				switch ((ret = SSL_shutdown(con->ssl))) {
  				case 1:
  					/* ok */
  					break;
  				case 0:
  					SSL_shutdown(con->ssl);
  					break;
  				default:
  					log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:", 
  							SSL_get_error(con->ssl, ret), 
  							ERR_error_string(ERR_get_error(), NULL));
  					return -1;
  				}
  			}
  #endif
  			
  			switch(con->mode) {
  			case DIRECT:
  #if 0
  				log_error_write(srv, __FILE__, __LINE__, "sd", 
  						"emergency exit: direct", 
  						con->fd);
  #endif
  				break;
  			default:
  				switch(r = plugins_call_handle_connection_close(srv, con)) {
  				case HANDLER_GO_ON:
  				case HANDLER_FINISHED:
  					break;
  				default:
  					log_error_write(srv, __FILE__, __LINE__, "");
  					break;
  				}
  				break;
  			}
  			
  			connection_reset(srv, con);
  			
  			/* close the connection */
  			if ((con->keep_alive == 1) &&
  			    (0 == shutdown(con->fd, SHUT_WR))) {
  				con->close_timeout_ts = srv->cur_ts;
  				connection_set_state(srv, con, CON_STATE_CLOSE);
  				
  				if (srv->srvconf.log_state_handling) {
  					log_error_write(srv, __FILE__, __LINE__, "sd", 
  							"shutdown for fd", con->fd);
  				}
  			} else {
  				connection_close(srv, con);
  			}
  			
  			con->keep_alive = 0;
  			
  			srv->con_closed++;
  			
  			break;
  		default:
  			log_error_write(srv, __FILE__, __LINE__, "sdd", 
  					"unknown state:", con->fd, con->state);
  			
  			break;
  		}
  		
  		if (done == -1) {
  			done = 0;
  		} else if (ostate == con->state) {
  			done = 1;
  		}
  	}
  
  	if (srv->srvconf.log_state_handling) {
  		log_error_write(srv, __FILE__, __LINE__, "sds", 
  				"state at exit:", 
  				con->fd,
  				connection_get_state(con->state));
  	}
  	
  	switch(con->state) {
  	case CON_STATE_READ_POST:
  	case CON_STATE_READ:
  	case CON_STATE_CLOSE:
  		fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_IN);
  		break;
  	case CON_STATE_WRITE:
  		/* request write-fdevent only if we really need it 
  		 * - if we have data to write
  		 * - if the socket is not writable yet 
  		 */
  		if (!chunkqueue_is_empty(con->write_queue) && 
  		    (con->is_writable == 0) &&
  		    (con->traffic_limit_reached == 0)) {
  			fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_OUT);
  		} else {
  			fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
  		}
  		break;
  	default:
  		fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
  		break;
  	}
  
  	return 0;
  }
  