/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