Index: lib/http_client.c
===================================================================
--- lib/http_client.c	(revision 41a94dd69dcbb5d4ef1fda5949196fed63994c03)
+++ lib/http_client.c	(revision ca8037e28d09ed96448509316a935eb130e6d3db)
@@ -2,5 +2,5 @@
   * BitlBee -- An IRC to other IM-networks gateway                     *
   *                                                                    *
-  * Copyright 2002-2012 Wilmer van der Gaast and others                *
+  * Copyright 2002-2013 Wilmer van der Gaast and others                *
   \********************************************************************/
 
@@ -69,4 +69,5 @@
 	req->request_length = strlen( request );
 	req->redir_ttl = 3;
+	req->content_length = -1;
 	
 	if( getenv( "BITLBEE_DEBUG" ) )
@@ -96,5 +97,4 @@
 	request = g_strdup_printf( "GET %s HTTP/1.0\r\n"
 	                           "Host: %s\r\n"
-	                           "Connection: close\r\n"
 	                           "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n"
 	                           "\r\n", url->file, url->host );
@@ -193,5 +193,14 @@
 }
 
+typedef enum {
+	CR_OK,
+	CR_EOF,
+	CR_ERROR,
+	CR_ABORT,
+} http_ret_t;
+
 static gboolean http_handle_headers( struct http_request *req );
+static http_ret_t http_process_chunked_data( struct http_request *req, const char *buffer, int len );
+static http_ret_t http_process_data( struct http_request *req, const char *buffer, int len );
 
 static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond )
@@ -199,6 +208,4 @@
 	struct http_request *req = data;
 	char buffer[4096];
-	char *s;
-	size_t content_length;
 	int st;
 	
@@ -244,50 +251,22 @@
 	}
 	
-	if( st > 0 && !req->sbuf )
-	{
-		req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + st + 1 );
-		memcpy( req->reply_headers + req->bytes_read, buffer, st );
-		req->bytes_read += st;
-		
-		st = 0;
-	}
-	
-	if( st >= 0 && ( req->flags & HTTPC_STREAMING ) )
-	{
-		if( !req->reply_body &&
-		    ( strstr( req->reply_headers, "\r\n\r\n" ) ||
-		      strstr( req->reply_headers, "\n\n" ) ) )
-		{
-			size_t hlen;
-			
-			/* We've now received all headers, so process them once
-			   before we start feeding back data. */
-			if( !http_handle_headers( req ) )
-				return FALSE;
-			
-			hlen = req->reply_body - req->reply_headers;
-			
-			req->sblen = req->bytes_read - hlen;
-			req->sbuf = g_memdup( req->reply_body, req->sblen + 1 );
-			req->reply_headers = g_realloc( req->reply_headers, hlen + 1 );
-			
-			req->reply_body = req->sbuf;
-		}
-		
-		if( st > 0 )
-		{
-			int pos = req->reply_body - req->sbuf;
-			req->sbuf = g_realloc( req->sbuf, req->sblen + st + 1 );
-			memcpy( req->sbuf + req->sblen, buffer, st );
-			req->bytes_read += st;
-			req->sblen += st;
-			req->sbuf[req->sblen] = '\0';
-			req->reply_body = req->sbuf + pos;
-			req->body_size = req->sblen - pos;
-		}
-		
-		if( req->reply_body )
-			req->func( req );
-	}
+	if( st > 0 )
+	{
+		http_ret_t c;
+		
+		if( req->flags & HTTPC_CHUNKED )
+			c = http_process_chunked_data( req, buffer, st );
+		else
+			c = http_process_data( req, buffer, st );
+		
+		if( c == CR_EOF )
+			goto eof;
+		else if( c == CR_ERROR || c == CR_ABORT )
+			return FALSE;
+	}
+	
+	if( req->content_length != -1 &&
+	    req->body_size >= req->content_length )
+		goto eof;
 	
 	if( ssl_pending( req->ssl ) )
@@ -311,12 +290,4 @@
 		goto cleanup;
 	}
-	
-	if( !( req->flags & HTTPC_STREAMING ) )
-	{
-		/* Returns FALSE if we were redirected, in which case we should abort
-		   and not run any callback yet. */
-		if( !http_handle_headers( req ) )
-			return FALSE;
-	}
 
 cleanup:
@@ -326,15 +297,10 @@
 		closesocket( req->fd );
 	
-	if( ( s = get_rfc822_header( req->reply_headers, "Content-Length", 0 ) ) &&
-	    sscanf( s, "%zd", &content_length ) == 1 )
-	{
-		if( content_length < req->body_size )
-		{
-			req->status_code = -1;
-			g_free( req->status_string );
-			req->status_string = g_strdup( "Response truncated" );
-		}
-	}
-	g_free( s );
+	if( req->body_size < req->content_length )
+	{
+		req->status_code = -1;
+		g_free( req->status_string );
+		req->status_string = g_strdup( "Response truncated" );
+	}
 	
 	if( getenv( "BITLBEE_DEBUG" ) && req )
@@ -347,9 +313,116 @@
 }
 
+static http_ret_t http_process_chunked_data( struct http_request *req, const char *buffer, int len )
+{
+	char *chunk, *eos, *s;
+	
+	if( len < 0 )
+		return TRUE;
+	
+	if( len > 0 )
+	{
+		req->cbuf = g_realloc( req->cbuf, req->cblen + len + 1 );
+		memcpy( req->cbuf + req->cblen, buffer, len );
+		req->cblen += len;
+		req->cbuf[req->cblen] = '\0';
+	}
+	
+	/* Turns out writing a proper chunked-encoding state machine is not
+	   that simple. :-( */
+	chunk = req->cbuf;
+	eos = req->cbuf + req->cblen;
+	while( TRUE )
+	{
+		int clen = 0;
+		
+		/* Might be a \r\n from the last chunk. */
+		s = chunk;
+		while( isspace( *s ) )
+			s ++;
+		/* Chunk length. Might be incomplete. */
+		if( s < eos && sscanf( s, "%x", &clen ) != 1 )
+			return CR_ERROR;
+		while( isxdigit( *s ) )
+			s ++;
+		
+		/* If we read anything here, it *must* be \r\n. */
+		if( strncmp( s, "\r\n", MIN( 2, eos - s ) ) != 0 )
+			return CR_ERROR;
+		s += 2;
+		
+		if( s >= eos )
+			break;
+		
+		/* 0-length chunk means end of response. */	
+		if( clen == 0 )
+			return CR_EOF;
+		
+		if( s + clen > eos )
+			break;
+		if( http_process_data( req, s, clen ) != CR_OK )
+			return CR_ABORT;
+		
+		chunk = s + clen;
+	}
+	
+	if( chunk != req->cbuf )
+	{
+		req->cblen = eos - chunk;
+		s = g_memdup( chunk, req->cblen + 1 );
+		g_free( req->cbuf );
+		req->cbuf = s;
+	}
+	
+	return CR_OK;
+}
+
+static http_ret_t http_process_data( struct http_request *req, const char *buffer, int len )
+{
+	if( len <= 0 )
+		return CR_OK;
+	
+	if( !req->reply_body )
+	{
+		req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + len + 1 );
+		memcpy( req->reply_headers + req->bytes_read, buffer, len );
+		req->bytes_read += len;
+		req->reply_headers[req->bytes_read] = '\0';
+		
+		if( strstr( req->reply_headers, "\r\n\r\n" ) ||
+		    strstr( req->reply_headers, "\n\n" ) )
+		{
+			/* We've now received all headers. Look for something
+			   interesting. */
+			if( !http_handle_headers( req ) )
+				return CR_ABORT;
+			
+			/* Start parsing the body as chunked if required. */
+			if( req->flags & HTTPC_CHUNKED )
+				return http_process_chunked_data( req, NULL, 0 );
+		}
+	}
+	else
+	{
+		int pos = req->reply_body - req->sbuf;
+		req->sbuf = g_realloc( req->sbuf, req->sblen + len + 1 );
+		memcpy( req->sbuf + req->sblen, buffer, len );
+		req->bytes_read += len;
+		req->sblen += len;
+		req->sbuf[req->sblen] = '\0';
+		req->reply_body = req->sbuf + pos;
+		req->body_size = req->sblen - pos;
+	}
+	
+	if( ( req->flags & HTTPC_STREAMING ) && req->reply_body )
+		req->func( req );
+	
+	return CR_OK;
+}
+
 /* Splits headers and body. Checks result code, in case of 300s it'll handle
    redirects. If this returns FALSE, don't call any callbacks! */
 static gboolean http_handle_headers( struct http_request *req )
 {
-	char *end1, *end2;
+	char *end1, *end2, *s;
 	int evil_server = 0;
 	
@@ -377,5 +450,5 @@
 	}
 	
-	*end1 = 0;
+	*end1 = '\0';
 	
 	if( getenv( "BITLBEE_DEBUG" ) )
@@ -387,5 +460,8 @@
 		req->reply_body = end1 + 2;
 	
-	req->body_size = req->reply_headers + req->bytes_read - req->reply_body;
+	/* Separately allocated space for headers and body. */
+	req->sblen = req->body_size = req->reply_headers + req->bytes_read - req->reply_body;
+	req->sbuf = req->reply_body = g_memdup( req->reply_body, req->body_size + 1 );
+	req->reply_headers = g_realloc( req->reply_headers, end1 - req->reply_headers + 1 );
 	
 	if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL )
@@ -452,5 +528,5 @@
 			   don't need this yet anyway, I won't implement it. */
 			
-			req->status_string = g_strdup( "Can't handle recursive redirects" );
+			req->status_string = g_strdup( "Can't handle relative redirects" );
 			
 			return TRUE;
@@ -508,5 +584,5 @@
 			
 			/* Okay, this isn't fun! We have to rebuild the request... :-( */
-			new_request = g_strdup_printf( "%s %s HTTP/1.0\r\nHost: %s%s",
+			new_request = g_strdup_printf( "%s %s HTTP/1.1\r\nHost: %s%s",
 			                               new_method, url->file, url->host, s );
 			
@@ -564,4 +640,23 @@
 		return FALSE;
 	}
+
+	if( ( s = get_rfc822_header( req->reply_headers, "Content-Length", 0 ) ) &&
+	    sscanf( s, "%d", &req->content_length ) != 1 )
+		req->content_length = -1;
+	g_free( s );
+	
+	if( ( s = get_rfc822_header( req->reply_headers, "Transfer-Encoding", 0 ) ) )
+	{
+		if( strcasestr( s, "chunked" ) )
+		{
+			req->flags |= HTTPC_CHUNKED;
+			req->cbuf = req->sbuf;
+			req->cblen = req->sblen;
+			
+			req->reply_body = req->sbuf = g_strdup( "" );
+			req->body_size = req->sblen = 0;
+		}
+		g_free( s );
+	}
 	
 	return TRUE;
@@ -607,4 +702,5 @@
 	g_free( req->status_string );
 	g_free( req->sbuf );
+	g_free( req->cbuf );
 	g_free( req );
 }
Index: lib/http_client.h
===================================================================
--- lib/http_client.h	(revision 41a94dd69dcbb5d4ef1fda5949196fed63994c03)
+++ lib/http_client.h	(revision ca8037e28d09ed96448509316a935eb130e6d3db)
@@ -42,4 +42,5 @@
 	HTTPC_STREAMING = 1,
 	HTTPC_EOF = 2,
+	HTTPC_CHUNKED = 4,
 	
 	/* Let's reserve 0x1000000+ for lib users. */
@@ -77,8 +78,13 @@
 	int bytes_written;
 	int bytes_read;
+	int content_length;     /* "Content-Length:" header or -1 */
 	
 	/* Used in streaming mode. Caller should read from reply_body. */
 	char *sbuf;
 	size_t sblen;
+	
+	/* Chunked encoding only. Raw chunked stream is decoded from here. */
+	char *cbuf;
+	size_t cblen;
 };
 
Index: lib/oauth.c
===================================================================
--- lib/oauth.c	(revision 41a94dd69dcbb5d4ef1fda5949196fed63994c03)
+++ lib/oauth.c	(revision ca8037e28d09ed96448509316a935eb130e6d3db)
@@ -262,5 +262,4 @@
 	                     "Content-Type: application/x-www-form-urlencoded\r\n"
 	                     "Content-Length: %zd\r\n"
-	                     "Connection: close\r\n"
 	                     "\r\n"
 	                     "%s", url_p.file, url_p.host, strlen( post ), post );
Index: lib/oauth2.c
===================================================================
--- lib/oauth2.c	(revision 41a94dd69dcbb5d4ef1fda5949196fed63994c03)
+++ lib/oauth2.c	(revision ca8037e28d09ed96448509316a935eb130e6d3db)
@@ -96,5 +96,4 @@
 	                     "Content-Type: application/x-www-form-urlencoded\r\n"
 	                     "Content-Length: %zd\r\n"
-	                     "Connection: close\r\n"
 	                     "\r\n"
 	                     "%s", url_p.file, url_p.host, strlen( args_s ), args_s );
Index: lib/proxy.c
===================================================================
--- lib/proxy.c	(revision 41a94dd69dcbb5d4ef1fda5949196fed63994c03)
+++ lib/proxy.c	(revision ca8037e28d09ed96448509316a935eb130e6d3db)
@@ -158,5 +158,5 @@
 		}
 
-		event_debug("proxy_connect_none( \"%s\", %d ) = %d\n", host, port, fd);
+		event_debug("proxy_connect_none( \"%s\", %d ) = %d\n", host, port_, fd);
 	
 		if (connect(fd, phb->gai_cur->ai_addr, phb->gai_cur->ai_addrlen) < 0 && !sockerr_again()) {
