/pidgin/main: f50c94ec0021: HTTP: content compression support (g...

Tomasz Wasilczyk tomkiewicz at cpw.pidgin.im
Wed Aug 14 10:17:14 EDT 2013


Changeset: f50c94ec0021128915ca5307fe14f26fea40bf05
Author:	 Tomasz Wasilczyk <tomkiewicz at cpw.pidgin.im>
Date:	 2013-08-14 16:17 +0200
Branch:	 default
URL: https://hg.pidgin.im/pidgin/main/rev/f50c94ec0021

Description:

HTTP: content compression support (gzip, deflate); added hard max length limit; fixed crashes after free; GnuTLS: treat GNUTLS_E_PREMATURE_TERMINATION indulgently

diffstat:

 libpurple/http.c                   |  216 +++++++++++++++++++++++++++++++++---
 libpurple/http.h                   |    3 +-
 libpurple/plugins/ssl/ssl-gnutls.c |    3 +
 3 files changed, 199 insertions(+), 23 deletions(-)

diffs (truncated from 384 to 300 lines):

diff --git a/libpurple/http.c b/libpurple/http.c
--- a/libpurple/http.c
+++ b/libpurple/http.c
@@ -32,13 +32,17 @@
 #include "debug.h"
 #include "ntlm.h"
 
+#include <zlib.h>
+
 #define PURPLE_HTTP_URL_CREDENTIALS_CHARS "a-z0-9.,~_/*!&%?=+\\^-"
 #define PURPLE_HTTP_MAX_RECV_BUFFER_LEN 10240
 #define PURPLE_HTTP_MAX_READ_BUFFER_LEN 10240
+#define PURPLE_HTTP_GZ_BUFF_LEN 1024
 
 #define PURPLE_HTTP_REQUEST_DEFAULT_MAX_REDIRECTS 20
 #define PURPLE_HTTP_REQUEST_DEFAULT_TIMEOUT 30
 #define PURPLE_HTTP_REQUEST_DEFAULT_MAX_LENGTH 1048576
+#define PURPLE_HTTP_REQUEST_HARD_MAX_LENGTH G_MAXINT32-1
 
 #define PURPLE_HTTP_PROGRESS_WATCHER_DEFAULT_INTERVAL 250000
 
@@ -50,6 +54,8 @@ typedef struct _PurpleHttpKeepaliveHost 
 
 typedef struct _PurpleHttpKeepaliveRequest PurpleHttpKeepaliveRequest;
 
+typedef struct _PurpleHttpGzStream PurpleHttpGzStream;
+
 typedef void (*PurpleHttpSocketConnectCb)(PurpleHttpSocket *hs,
 	const gchar *error, gpointer user_data);
 
@@ -89,7 +95,7 @@ struct _PurpleHttpRequest
 	int timeout;
 	int max_redirects;
 	gboolean http11;
-	int max_length;
+	guint max_length;
 };
 
 struct _PurpleHttpConnection
@@ -112,13 +118,14 @@ struct _PurpleHttpConnection
 	int request_header_written, request_contents_written;
 	gboolean main_header_got, headers_got;
 	GString *response_buffer;
+	PurpleHttpGzStream *gz_stream;
 
 	GString *contents_reader_buffer;
 	gboolean contents_reader_requested;
 
 	int redirects_count;
 
-	int length_expected, length_got;
+	int length_expected, length_got, length_got_decompressed;
 
 	gboolean is_chunked, in_chunk, chunks_done;
 	int chunk_length, chunk_got;
@@ -216,6 +223,15 @@ struct _PurpleHttpConnectionSet
 	GHashTable *connections;
 };
 
+struct _PurpleHttpGzStream
+{
+	gboolean failed;
+	z_stream zs;
+	gsize max_output;
+	gsize decompressed;
+	GString *pending;
+};
+
 static time_t purple_http_rfc1123_to_time(const gchar *str);
 
 static gboolean purple_http_request_is_method(PurpleHttpRequest *request,
@@ -331,6 +347,120 @@ static time_t purple_http_rfc1123_to_tim
 	return t;
 }
 
+/*** GZip streams *************************************************************/
+
+static PurpleHttpGzStream *
+purple_http_gz_new(gsize max_output, gboolean is_deflate)
+{
+	PurpleHttpGzStream *gzs = g_new0(PurpleHttpGzStream, 1);
+	int windowBits;
+
+	if (is_deflate)
+		windowBits = -MAX_WBITS;
+	else /* is gzip */
+		windowBits = MAX_WBITS + 32;
+
+	if (inflateInit2(&gzs->zs, windowBits) != Z_OK) {
+		purple_debug_error("http", "Cannot initialize zlib stream\n");
+		g_free(gzs);
+		return NULL;
+	}
+
+	gzs->max_output = max_output;
+
+	return gzs;
+}
+
+static GString *
+purple_http_gz_put(PurpleHttpGzStream *gzs, const gchar *buf, gsize len)
+{
+	const gchar *compressed_buff;
+	gsize compressed_len;
+	GString *ret;
+	z_stream *zs;
+
+	g_return_val_if_fail(gzs != NULL, NULL);
+	g_return_val_if_fail(buf != NULL, NULL);
+
+	if (gzs->failed)
+		return NULL;
+
+	zs = &gzs->zs;
+
+	if (gzs->pending) {
+		g_string_append_len(gzs->pending, buf, len);
+		compressed_buff = gzs->pending->str;
+		compressed_len = gzs->pending->len;
+	} else {
+		compressed_buff = buf;
+		compressed_len = len;
+	}
+
+	zs->next_in = (z_const Bytef*)compressed_buff;
+	zs->avail_in = compressed_len;
+
+	ret = g_string_new(NULL);
+	while (zs->avail_in > 0) {
+		int gzres;
+		gchar decompressed_buff[PURPLE_HTTP_GZ_BUFF_LEN];
+		gsize decompressed_len;
+
+		zs->next_out = (Bytef*)decompressed_buff;
+		zs->avail_out = sizeof(decompressed_buff);
+		decompressed_len = zs->avail_out = sizeof(decompressed_buff);
+		gzres = inflate(zs, Z_FULL_FLUSH);
+		decompressed_len -= zs->avail_out;
+
+		if (gzres == Z_OK || gzres == Z_STREAM_END) {
+			if (decompressed_len == 0)
+				break;
+			if (gzs->decompressed + decompressed_len >=
+				gzs->max_output)
+			{
+				purple_debug_warning("http", "Maximum amount of"
+					" decompressed data is reached\n");
+				decompressed_len = gzs->max_output -
+					gzs->decompressed;
+				gzres = Z_STREAM_END;
+			}
+			gzs->decompressed += decompressed_len;
+			g_string_append_len(ret, decompressed_buff,
+				decompressed_len);
+			if (gzres == Z_STREAM_END)
+				break;
+		} else {
+			purple_debug_error("http",
+				"Decompression failed (%d): %s\n", gzres,
+				zs->msg);
+			gzs->failed = TRUE;
+			return NULL;
+		}
+	}
+
+	if (gzs->pending) {
+		g_string_free(gzs->pending, TRUE);
+		gzs->pending = NULL;
+	}
+
+	if (zs->avail_in > 0) {
+		gzs->pending = g_string_new_len((gchar*)zs->next_in,
+			zs->avail_in);
+	}
+
+	return ret;
+}
+
+static void
+purple_http_gz_free(PurpleHttpGzStream *gzs)
+{
+	if (gzs == NULL)
+		return;
+	inflateEnd(&gzs->zs);
+	if (gzs->pending)
+		g_string_free(gzs->pending, TRUE);
+	g_free(gzs);
+}
+
 /*** HTTP Sockets *************************************************************/
 
 static void _purple_http_socket_connected_raw(gpointer _hs, gint fd,
@@ -778,6 +908,8 @@ static void _purple_http_gen_headers(Pur
 	}
 	if (!purple_http_headers_get(hdrs, "accept"))
 		g_string_append(h, "Accept: */*\r\n");
+	if (!purple_http_headers_get(hdrs, "accept-encoding"))
+		g_string_append(h, "Accept-Encoding: gzip, deflate\r\n");
 
 	if (!purple_http_headers_get(hdrs, "content-length") && (
 		req->contents_length > 0 ||
@@ -930,31 +1062,51 @@ static gboolean _purple_http_recv_header
 static gboolean _purple_http_recv_body_data(PurpleHttpConnection *hc,
 	const gchar *buf, int len)
 {
-	int current_offset = hc->length_got;
+	GString *decompressed = NULL;
 
 	if (hc->length_expected >= 0 &&
 		len + hc->length_got > hc->length_expected)
 	{
 		len = hc->length_expected - hc->length_got;
 	}
-	if (hc->request->max_length >= 0) {
-		if (hc->length_got + len > hc->request->max_length) {
-			purple_debug_warning("http",
-				"Maximum length exceeded, truncating\n");
-			len = hc->request->max_length - hc->length_got;
-			hc->length_expected = hc->request->max_length;
+
+	hc->length_got += len;
+
+	if (hc->gz_stream != NULL) {
+		decompressed = purple_http_gz_put(hc->gz_stream, buf, len);
+		if (decompressed == NULL) {
+			_purple_http_error(hc,
+				_("Error while decompressing data"));
+			return FALSE;
 		}
+		buf = decompressed->str;
+		len = decompressed->len;
 	}
-	hc->length_got += len;
-
-	if (len == 0)
+
+	g_assert(hc->request->max_length <=
+		PURPLE_HTTP_REQUEST_HARD_MAX_LENGTH);
+	if (hc->length_got_decompressed + len > hc->request->max_length) {
+		purple_debug_warning("http",
+			"Maximum length exceeded, truncating\n");
+		len = hc->request->max_length - hc->length_got_decompressed;
+		hc->length_expected = hc->length_got;
+	}
+	hc->length_got_decompressed += len;
+
+	if (len == 0) {
+		if (decompressed != NULL)
+			g_string_free(decompressed, TRUE);
 		return TRUE;
+	}
 
 	if (hc->request->response_writer != NULL) {
 		gboolean succ;
 		succ = hc->request->response_writer(hc, hc->response, buf,
-			current_offset, len, hc->request->response_writer_data);
+			hc->length_got_decompressed, len,
+			hc->request->response_writer_data);
 		if (!succ) {
+			if (decompressed != NULL)
+				g_string_free(decompressed, TRUE);
 			purple_debug_error("http",
 				"Cannot write using callback\n");
 			_purple_http_error(hc,
@@ -967,6 +1119,9 @@ static gboolean _purple_http_recv_body_d
 		g_string_append_len(hc->response->contents, buf, len);
 	}
 
+	if (decompressed != NULL)
+		g_string_free(decompressed, TRUE);
+
 	purple_http_conn_notify_progress_watcher(hc);
 	return TRUE;
 }
@@ -1128,12 +1283,25 @@ static gboolean _purple_http_recv_loopbo
 			return FALSE;
 		len = 0;
 		if (hc->headers_got) {
+			gboolean is_gzip, is_deflate;
 			if (!purple_http_headers_get_int(hc->response->headers,
 				"Content-Length", &hc->length_expected))
 				hc->length_expected = -1;
 			hc->is_chunked = (purple_http_headers_match(
 				hc->response->headers,
 				"Transfer-Encoding", "chunked"));
+			is_gzip = purple_http_headers_match(
+				hc->response->headers, "Content-Encoding",
+				"gzip");
+			is_deflate = purple_http_headers_match(
+				hc->response->headers, "Content-Encoding",
+				"deflate");
+			if (is_gzip || is_deflate)
+			{
+				hc->gz_stream = purple_http_gz_new(
+					hc->request->max_length + 1,
+					is_deflate);
+			}
 		}
 		if (hc->headers_got && hc->response_buffer &&
 			hc->response_buffer->len > 0) {



More information about the Commits mailing list