Merge heads.

Mon, 05 Nov 2012 18:15:24 -0500

author
Elliott Sales de Andrade <qulogic@pidgin.im>
date
Mon, 05 Nov 2012 18:15:24 -0500
changeset 33515
e6fb7d0dae03
parent 33514
d6f30dcbfd7e (current diff)
parent 33470
0f5fd72228e3 (diff)
child 33516
ba891043ec97
child 33517
ddd9e37c4b07

Merge heads.

configure.ac file | annotate | diff | comparison | revisions
libpurple/Makefile.am file | annotate | diff | comparison | revisions
pidgin/gtkprefs.c file | annotate | diff | comparison | revisions
--- a/configure.ac	Sun Oct 07 00:11:52 2012 -0400
+++ b/configure.ac	Mon Nov 05 18:15:24 2012 -0500
@@ -821,11 +821,8 @@
 
 PKG_CHECK_MODULES([JSON], [json-glib-1.0], [enable_json="yes"], [enable_json="no"])
 
-dnl #######################################################################
-dnl # Check for libcurl (required)
-dnl #######################################################################
-
-PKG_CHECK_MODULES([LIBCURL], [libcurl])
+AC_SUBST(JSON_CFLAGS)
+AC_SUBST(JSON_LIBS)
 
 dnl #######################################################################
 dnl # Check for zlib (required)
--- a/finch/plugins/gnttinyurl.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/finch/plugins/gnttinyurl.c	Mon Nov 05 18:15:24 2012 -0500
@@ -21,6 +21,7 @@
 
 #include "internal.h"
 #include <glib.h>
+#include "obsolete.h"
 
 #define PLUGIN_STATIC_NAME	TinyURL
 #define PREFS_BASE          "/plugins/gnt/tinyurl"
--- a/libpurple/Makefile.am	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/Makefile.am	Mon Nov 05 18:15:24 2012 -0500
@@ -50,6 +50,7 @@
 	desktopitem.c \
 	eventloop.c \
 	ft.c \
+	http.c \
 	idle.c \
 	imgstore.c \
 	log.c \
@@ -65,6 +66,7 @@
 	network.c \
 	ntlm.c \
 	notify.c \
+	obsolete.c \
 	plugin.c \
 	pluginpref.c \
 	pounce.c \
@@ -283,6 +285,7 @@
 
 noinst_HEADERS= \
 	internal.h \
+	obsolete.h \
 	media/backend-fs2.h \
 	valgrind.h
 
--- a/libpurple/connection.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/connection.c	Mon Nov 05 18:15:24 2012 -0500
@@ -31,6 +31,7 @@
 #include "connection.h"
 #include "dbus-maybe.h"
 #include "debug.h"
+#include "http.h"
 #include "log.h"
 #include "notify.h"
 #include "prefs.h"
@@ -251,6 +252,7 @@
 
 	update_keepalive(gc, FALSE);
 
+	purple_http_conn_cancel_all(gc);
 	purple_proxy_connect_cancel_with_handle(gc);
 
 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
--- a/libpurple/core.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/core.c	Mon Nov 05 18:15:24 2012 -0500
@@ -33,6 +33,7 @@
 #include "debug.h"
 #include "dnsquery.h"
 #include "ft.h"
+#include "http.h"
 #include "idle.h"
 #include "imgstore.h"
 #include "network.h"
@@ -174,6 +175,7 @@
 	purple_stun_init();
 	purple_xfers_init();
 	purple_idle_init();
+	purple_http_init();
 	purple_smileys_init();
 	/*
 	 * Call this early on to try to auto-detect our IP address and
@@ -222,6 +224,7 @@
 
 	/* Save .xml files, remove signals, etc. */
 	purple_smileys_uninit();
+	purple_http_uninit();
 	purple_idle_uninit();
 	purple_pounces_uninit();
 	purple_blist_uninit();
--- a/libpurple/ft.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/ft.c	Mon Nov 05 18:15:24 2012 -0500
@@ -319,7 +319,7 @@
 }
 
 void
-purple_xfer_conversation_write(PurpleXfer *xfer, gchar *message,
+purple_xfer_conversation_write(PurpleXfer *xfer, const gchar *message,
 	gboolean is_error)
 {
 	purple_xfer_conversation_write_internal(xfer, message, is_error, FALSE);
--- a/libpurple/ft.h	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/ft.h	Mon Nov 05 18:15:24 2012 -0500
@@ -705,7 +705,7 @@
  * @param message The message to display.
  * @param is_error Is this an error message?.
  */
-void purple_xfer_conversation_write(PurpleXfer *xfer, char *message, gboolean is_error);
+void purple_xfer_conversation_write(PurpleXfer *xfer, const gchar *message, gboolean is_error);
 
 /**
  * Allows the UI to signal it's ready to send/receive data (depending on
--- a/libpurple/glibcompat.h	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/glibcompat.h	Mon Nov 05 18:15:24 2012 -0500
@@ -25,31 +25,34 @@
  * Also, any public API should not depend on this file.
  */
 
+#if !GLIB_CHECK_VERSION(2, 20, 0)
+
+#define G_OFFSET_FORMAT G_GINT64_FORMAT
+
 #if !GLIB_CHECK_VERSION(2, 28, 0)
 
+static inline gint64 g_get_monotonic_time(void)
+{
+	GTimeVal time_s;
+
+	g_get_current_time(&time_s);
+
+	return ((gint64)time_s.tv_sec << 32) | time_s.tv_usec;
+}
+
 static inline void g_list_free_full(GList *list, GDestroyNotify free_func)
 {
-	GList *it;
-	it = g_list_first(list);
-	while (it)
-	{
-		free_func(it->data);
-		it = g_list_next(it);
-	}
+	g_list_foreach(list, (GFunc)free_func, NULL);
 	g_list_free(list);
 }
 
 static inline void g_slist_free_full(GSList *list, GDestroyNotify free_func)
 {
-	GSList *it = list;
-	while (it)
-	{
-		free_func(it->data);
-		it = g_slist_next(it);
-	}
+	g_slist_foreach(list, (GFunc)free_func, NULL);
 	g_slist_free(list);
 }
 
 #endif /* 2.28.0 */
+#endif /* 2.20.0 */
 
 #endif /* _PIDGINGLIBCOMPAT_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/http.c	Mon Nov 05 18:15:24 2012 -0500
@@ -0,0 +1,2286 @@
+/**
+ * @file http.c HTTP API
+ * @ingroup core
+ */
+
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include "http.h"
+
+#include "internal.h"
+
+#include "debug.h"
+#include "ntlm.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_REQUEST_DEFAULT_MAX_REDIRECTS 20
+#define PURPLE_HTTP_REQUEST_DEFAULT_TIMEOUT 30
+
+typedef struct _PurpleHttpURL PurpleHttpURL;
+
+typedef struct _PurpleHttpSocket PurpleHttpSocket;
+
+typedef struct _PurpleHttpHeaders PurpleHttpHeaders;
+
+struct _PurpleHttpSocket
+{
+	gboolean is_ssl;
+	PurpleSslConnection *ssl_connection;
+	PurpleProxyConnectData *raw_connection;
+	int fd;
+	guint inpa;
+};
+
+struct _PurpleHttpRequest
+{
+	int ref_count;
+
+	gchar *url;
+	gchar *method;
+	PurpleHttpHeaders *headers;
+	PurpleHttpCookieJar *cookie_jar;
+
+	gchar *contents;
+	int contents_length;
+	PurpleHttpContentReader contents_reader;
+	gpointer contents_reader_data;
+
+	int timeout;
+	int max_redirects;
+	gboolean http11;
+	int max_length;
+};
+
+struct _PurpleHttpConnection
+{
+	PurpleConnection *gc;
+	PurpleHttpCallback callback;
+	gpointer user_data;
+	gboolean is_reading;
+
+	PurpleHttpURL *url;
+	PurpleHttpRequest *request;
+	PurpleHttpResponse *response;
+
+	PurpleHttpSocket socket;
+	GString *request_header;
+	int request_header_written, request_contents_written;
+	gboolean main_header_got, headers_got;
+	GString *response_buffer;
+
+	GString *contents_reader_buffer;
+	gboolean contents_reader_requested;
+
+	int redirects_count;
+	int data_length_got;
+
+	int length_expected, length_got;
+
+	gboolean is_chunked, in_chunk, chunks_done;
+	int chunk_length, chunk_got;
+
+	GList *link_global, *link_gc;
+
+	guint timeout_handle;
+
+	PurpleHttpProgressWatcher watcher;
+	gpointer watcher_user_data;
+	guint watcher_interval_threshold;
+	gint64 watcher_last_call;
+};
+
+struct _PurpleHttpResponse
+{
+	int code;
+	gchar *error;
+
+	GString *contents;
+	PurpleHttpHeaders *headers;
+};
+
+struct _PurpleHttpURL
+{
+	gchar *protocol;
+	gchar *user;
+	gchar *password;
+	gchar *host;
+	int port;
+	gchar *path;
+	gchar *fragment;
+};
+
+struct _PurpleHttpHeaders
+{
+	GList *list;
+	GHashTable *by_name;
+};
+
+typedef struct
+{
+	time_t expires;
+	gchar *value;
+} PurpleHttpCookie;
+
+struct _PurpleHttpCookieJar
+{
+	int ref_count;
+
+	GHashTable *tab;
+};
+
+static time_t purple_http_rfc1123_to_time(const gchar *str);
+
+static PurpleHttpConnection * purple_http_connection_new(
+	PurpleHttpRequest *request, PurpleConnection *gc);
+static void purple_http_connection_terminate(PurpleHttpConnection *hc);
+static void purple_http_conn_notify_progress_watcher(PurpleHttpConnection *hc);
+
+static PurpleHttpResponse * purple_http_response_new(void);
+static void purple_http_response_free(PurpleHttpResponse *response);
+
+static void purple_http_cookie_jar_parse(PurpleHttpCookieJar *cookie_jar,
+	GList *values);
+static gchar * purple_http_cookie_jar_gen(PurpleHttpCookieJar *cookie_jar);
+gchar * purple_http_cookie_jar_dump(PurpleHttpCookieJar *cjar);
+
+static PurpleHttpURL * purple_http_url_parse(const char *url);
+static void purple_http_url_free(PurpleHttpURL *parsed_url);
+static void purple_http_url_relative(PurpleHttpURL *base_url,
+	PurpleHttpURL *relative_url);
+static gchar * purple_http_url_print(PurpleHttpURL *parsed_url);
+
+static GRegex *purple_http_re_url, *purple_http_re_url_host,
+	*purple_http_re_rfc1123;
+
+/**
+ * Values: pointers to running PurpleHttpConnection.
+ */
+static GList *purple_http_hc_list;
+
+/**
+ * Keys: pointers to PurpleConnection.
+ * Values: GList of pointers to running PurpleHttpConnection.
+ */
+static GHashTable *purple_http_hc_by_gc;
+
+/**
+ * Keys: pointers to PurpleHttpConnection.
+ * Values: pointers to links in purple_http_hc_list.
+ */
+static GHashTable *purple_http_hc_by_ptr;
+
+/*** Helper functions *********************************************************/
+
+static time_t purple_http_rfc1123_to_time(const gchar *str)
+{
+	static const gchar *months[13] = {"jan", "feb", "mar", "apr", "may", "jun",
+		"jul", "aug", "sep", "oct", "nov", "dec", NULL};
+	GMatchInfo *match_info;
+	gchar *d_date, *d_month, *d_year, *d_time;
+	int month;
+	gchar *iso_date;
+	time_t t;
+
+	g_return_val_if_fail(str != NULL, 0);
+
+	g_regex_match(purple_http_re_rfc1123, str, 0, &match_info);
+	if (!g_match_info_matches(match_info)) {
+		g_match_info_free(match_info);
+		return 0;
+	}
+	g_match_info_free(match_info);
+
+	d_date = g_match_info_fetch(match_info, 1);
+	d_month = g_match_info_fetch(match_info, 2);
+	d_year = g_match_info_fetch(match_info, 3);
+	d_time = g_match_info_fetch(match_info, 4);
+
+	month = 0;
+	while (months[month] != NULL)
+	{
+		if (0 == g_ascii_strcasecmp(d_month, months[month]))
+			break;
+		month++;
+	}
+	month++;
+
+	iso_date = g_strdup_printf("%s-%02d-%sT%s+00:00", 
+		d_year, month, d_date, d_time);
+
+	g_free(d_date);
+	g_free(d_month);
+	g_free(d_year);
+	g_free(d_time);
+
+	if (month > 12) {
+		purple_debug_warning("http", "Invalid month: %s\n", d_month);
+		g_free(iso_date);
+		return 0;
+	}
+
+	t = purple_str_to_time(iso_date, TRUE, NULL, NULL, NULL);
+
+	g_free(iso_date);
+
+	return t;
+}
+
+/*** Headers collection *******************************************************/
+
+static PurpleHttpHeaders * purple_http_headers_new(void);
+static void purple_http_headers_free(PurpleHttpHeaders *hdrs);
+static void purple_http_headers_add(PurpleHttpHeaders *hdrs, const gchar *key,
+	const gchar *value);
+static const GList * purple_http_headers_get_all(PurpleHttpHeaders *hdrs);
+static GList * purple_http_headers_get_all_by_name(
+	PurpleHttpHeaders *hdrs, const gchar *key);
+static const gchar * purple_http_headers_get(PurpleHttpHeaders *hdrs,
+	const gchar *key);
+static gboolean purple_http_headers_get_int(PurpleHttpHeaders *hdrs,
+	const gchar *key, int *dst);
+static gboolean purple_http_headers_match(PurpleHttpHeaders *hdrs,
+	const gchar *key, const gchar *value);
+static gchar * purple_http_headers_dump(PurpleHttpHeaders *hdrs);
+
+static PurpleHttpHeaders * purple_http_headers_new(void)
+{
+	PurpleHttpHeaders *hdrs = g_new0(PurpleHttpHeaders, 1);
+
+	hdrs->by_name = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+		(GDestroyNotify)g_list_free);
+
+	return hdrs;
+}
+
+static void purple_http_headers_free_kvp(PurpleKeyValuePair *kvp)
+{
+	g_free(kvp->key);
+	g_free(kvp->value);
+	g_free(kvp);
+}
+
+static void purple_http_headers_free(PurpleHttpHeaders *hdrs)
+{
+	if (hdrs == NULL)
+		return;
+
+	g_hash_table_destroy(hdrs->by_name);
+	g_list_free_full(hdrs->list,
+		(GDestroyNotify)purple_http_headers_free_kvp);
+	g_free(hdrs);
+}
+
+static void purple_http_headers_add(PurpleHttpHeaders *hdrs, const gchar *key,
+	const gchar *value)
+{
+	PurpleKeyValuePair *kvp;
+	GList *named_values, *new_values;
+	gchar *key_low;
+
+	g_return_if_fail(hdrs != NULL);
+	g_return_if_fail(key != NULL);
+	g_return_if_fail(value != NULL);
+
+	kvp = g_new0(PurpleKeyValuePair, 1);
+	kvp->key = g_strdup(key);
+	kvp->value = g_strdup(value);
+	hdrs->list = g_list_append(hdrs->list, kvp);
+
+	key_low = g_ascii_strdown(key, -1);
+	named_values = g_hash_table_lookup(hdrs->by_name, key_low);
+	new_values = g_list_append(named_values, kvp->value);
+	if (named_values)
+		g_free(key_low);
+	else
+		g_hash_table_insert(hdrs->by_name, key_low, new_values);
+}
+
+static void purple_http_headers_remove(PurpleHttpHeaders *hdrs,
+	const gchar *key)
+{
+	GList *it, *curr;
+
+	g_return_if_fail(hdrs != NULL);
+	g_return_if_fail(key != NULL);
+
+	if (!g_hash_table_remove(hdrs->by_name, key))
+		return;
+
+	/* Could be optimized to O(1). */
+	it = g_list_first(hdrs->list);
+	while (it)
+	{
+		PurpleKeyValuePair *kvp = it->data;
+		curr = it;
+		it = g_list_next(it);
+		if (g_ascii_strcasecmp(kvp->key, key) != 0)
+			continue;
+
+		hdrs->list = g_list_delete_link(hdrs->list, curr);
+		purple_http_headers_free_kvp(kvp);
+	}
+}
+
+static const GList * purple_http_headers_get_all(PurpleHttpHeaders *hdrs)
+{
+	return hdrs->list;
+}
+
+/* return const */
+static GList * purple_http_headers_get_all_by_name(
+	PurpleHttpHeaders *hdrs, const gchar *key)
+{
+	GList *values;
+	gchar *key_low;
+
+	g_return_val_if_fail(hdrs != NULL, NULL);
+	g_return_val_if_fail(key != NULL, NULL);
+
+	key_low = g_ascii_strdown(key, -1);
+	values = g_hash_table_lookup(hdrs->by_name, key_low);
+	g_free(key_low);
+
+	return values;
+}
+
+static const gchar * purple_http_headers_get(PurpleHttpHeaders *hdrs,
+	const gchar *key)
+{
+	const GList *values = purple_http_headers_get_all_by_name(hdrs, key);
+
+	if (!values)
+		return NULL;
+
+	return values->data;
+}
+
+static gboolean purple_http_headers_get_int(PurpleHttpHeaders *hdrs,
+	const gchar *key, int *dst)
+{
+	int val;
+	const gchar *str;
+
+	str = purple_http_headers_get(hdrs, key);
+	if (!str)
+		return FALSE;
+
+	if (1 != sscanf(str, "%d", &val))
+		return FALSE;
+
+	*dst = val;
+	return TRUE;
+}
+
+static gboolean purple_http_headers_match(PurpleHttpHeaders *hdrs,
+	const gchar *key, const gchar *value)
+{
+	const gchar *str;
+
+	str = purple_http_headers_get(hdrs, key);
+	if (str == NULL || value == NULL)
+		return str == value;
+
+	return (g_ascii_strcasecmp(str, value) == 0);
+}
+
+static gchar * purple_http_headers_dump(PurpleHttpHeaders *hdrs)
+{
+	const GList *hdr;
+
+	GString *s = g_string_new("");
+	
+	hdr = purple_http_headers_get_all(hdrs);
+	while (hdr) {
+		PurpleKeyValuePair *kvp = hdr->data;
+		hdr = g_list_next(hdr);
+
+		g_string_append_printf(s, "%s: %s%s", kvp->key,
+			(gchar*)kvp->value, hdr ? "\n" : "");
+	}
+	
+	return g_string_free(s, FALSE);
+}
+
+/*** HTTP protocol backend ****************************************************/
+
+static void _purple_http_disconnect(PurpleHttpConnection *hc);
+
+static void _purple_http_gen_headers(PurpleHttpConnection *hc);
+static void _purple_http_recv(gpointer _hc, gint fd, PurpleInputCondition cond);
+static void _purple_http_recv_ssl(gpointer _hc,
+	PurpleSslConnection *ssl_connection, PurpleInputCondition cond);
+static void _purple_http_send(gpointer _hc, gint fd, PurpleInputCondition cond);
+
+static void _purple_http_connected_raw(gpointer _hc, gint source,
+	const gchar *error_message);
+static void _purple_http_connected_ssl(gpointer _hc,
+	PurpleSslConnection *ssl_connection, PurpleInputCondition cond);
+static void _purple_http_connected_ssl_error(
+	PurpleSslConnection *ssl_connection, PurpleSslErrorType error,
+	gpointer _hc);
+
+/* closes current connection (if exists), estabilishes one and proceeds with
+ * request */
+static gboolean _purple_http_reconnect(PurpleHttpConnection *hc);
+
+static void _purple_http_error(PurpleHttpConnection *hc, const char *format,
+	...) G_GNUC_PRINTF(2, 3);
+
+static void _purple_http_error(PurpleHttpConnection *hc, const char *format,
+	...)
+{
+	va_list args;
+
+	va_start(args, format);
+	hc->response->error = g_strdup_vprintf(format, args);
+	va_end(args);
+
+	purple_http_conn_cancel(hc);
+}
+
+static void _purple_http_gen_headers(PurpleHttpConnection *hc)
+{
+	GString *h;
+	PurpleHttpURL *url;
+	const GList *hdr;
+	PurpleHttpRequest *req;
+	PurpleHttpHeaders *hdrs;
+	gchar *request_url, *tmp_url = NULL;
+
+	PurpleProxyInfo *proxy;
+	gboolean proxy_http = FALSE;
+	const gchar *proxy_username, *proxy_password;
+
+	g_return_if_fail(hc != NULL);
+
+	if (hc->request_header != NULL)
+		return;
+
+	req = hc->request;
+	url = hc->url;
+	hdrs = req->headers;
+	proxy = purple_proxy_get_setup(hc->gc ?
+		purple_connection_get_account(hc->gc) : NULL);
+
+	proxy_http = (purple_proxy_info_get_type(proxy) == PURPLE_PROXY_HTTP ||
+		purple_proxy_info_get_type(proxy) == PURPLE_PROXY_USE_ENVVAR);
+	/* this is HTTP proxy, but used with tunelling with CONNECT */
+	if (proxy_http && url->port != 80)
+		proxy_http = FALSE;
+
+	hc->request_header = h = g_string_new("");
+	hc->request_header_written = 0;
+	hc->request_contents_written = 0;
+
+	if (proxy_http)
+		request_url = tmp_url = purple_http_url_print(url);
+	else
+		request_url = url->path;
+
+	g_string_append_printf(h, "%s %s HTTP/%s\r\n",
+		req->method ? req->method : "GET",
+		request_url,
+		req->http11 ? "1.1" : "1.0");
+
+	if (tmp_url)
+		g_free(tmp_url);
+
+	if (!purple_http_headers_get(hdrs, "host"))
+		g_string_append_printf(h, "Host: %s\r\n", url->host);
+	if (!purple_http_headers_get(hdrs, "connection"))
+		g_string_append(h, "Connection: close\r\n");
+	if (!purple_http_headers_get(hdrs, "accept"))
+		g_string_append(h, "Accept: */*\r\n");
+
+	if (req->contents_length > 0 && !purple_http_headers_get(hdrs,
+		"content-length"))
+		g_string_append_printf(h, "Content-Length: %u\r\n",
+			req->contents_length);
+
+	if (proxy_http)
+		g_string_append(h, "Proxy-Connection: close\r\n");
+
+	proxy_username = purple_proxy_info_get_username(proxy);
+	if (proxy_http && proxy_username != NULL && proxy_username[0] != '\0') {
+		gchar *proxy_auth, *ntlm_type1, *tmp;
+		int len;
+
+		proxy_password = purple_proxy_info_get_password(proxy);
+		if (proxy_password == NULL)
+			proxy_password = "";
+
+		tmp = g_strdup_printf("%s:%s", proxy_username, proxy_password);
+		len = strlen(tmp);
+		proxy_auth = purple_base64_encode((const guchar *)tmp, len);
+		memset(tmp, 0, len);
+		g_free(tmp);
+
+		ntlm_type1 = purple_ntlm_gen_type1(purple_get_host_name(), "");
+
+		g_string_append_printf(h, "Proxy-Authorization: Basic %s\r\n",
+			proxy_auth);
+		g_string_append_printf(h, "Proxy-Authorization: NTLM %s\r\n",
+			ntlm_type1);
+		g_string_append(h, "Proxy-Connection: Close\r\n");
+
+		memset(proxy_auth, 0, strlen(proxy_auth));
+		g_free(proxy_auth);
+		g_free(ntlm_type1);
+	}
+
+	hdr = purple_http_headers_get_all(hdrs);
+	while (hdr) {
+		PurpleKeyValuePair *kvp = hdr->data;
+		hdr = g_list_next(hdr);
+
+		g_string_append_printf(h, "%s: %s\r\n",
+			kvp->key, (gchar*)kvp->value);
+	}
+
+	if (!purple_http_cookie_jar_is_empty(req->cookie_jar)) {
+		gchar * cookies = purple_http_cookie_jar_gen(req->cookie_jar);
+		g_string_append_printf(h, "Cookie: %s\r\n", cookies);
+		g_free(cookies);
+	}
+
+	g_string_append_printf(h, "\r\n");
+
+	if (purple_debug_is_unsafe() && purple_debug_is_verbose()) {
+		purple_debug_misc("http", "Generated request headers:\n%s",
+			h->str);
+	}
+}
+
+static gboolean _purple_http_recv_headers(PurpleHttpConnection *hc,
+	const gchar *buf, int len)
+{
+	gchar *eol, *delim;
+
+	if (hc->headers_got) {
+		purple_debug_error("http", "Headers already got\n");
+		_purple_http_error(hc, _("Error parsing HTTP"));
+		return FALSE;
+	}
+
+	g_string_append_len(hc->response_buffer, buf, len);
+	if (hc->response_buffer->len > PURPLE_HTTP_MAX_RECV_BUFFER_LEN) {
+		purple_debug_error("http",
+			"Buffer too big when parsing headers\n");
+		_purple_http_error(hc, _("Error parsing HTTP"));
+		return FALSE;
+	}
+
+	while ((eol = strstr(hc->response_buffer->str, "\r\n"))
+		!= NULL) {
+		gchar *hdrline = hc->response_buffer->str;
+		int hdrline_len = eol - hdrline;
+
+		hdrline[hdrline_len] = '\0';
+
+		if (hdrline[0] == '\0') {
+			if (!hc->main_header_got) {
+				hc->response->code = 0;
+				purple_debug_warning("http",
+					"Main header not present\n");
+				_purple_http_error(hc, _("Error parsing HTTP"));
+				return FALSE;
+			}
+			hc->headers_got = TRUE;
+			if (purple_debug_is_verbose())
+				purple_debug_misc("http", "Got headers end\n");
+		} else if (!hc->main_header_got) {
+			hc->main_header_got = TRUE;
+			delim = strchr(hdrline, ' ');
+			if (delim == NULL || 1 != sscanf(delim + 1, "%d",
+				&hc->response->code)) {
+				purple_debug_warning("http",
+					"Invalid response code\n");
+				_purple_http_error(hc, _("Error parsing HTTP"));
+				return FALSE;
+			}
+			if (purple_debug_is_verbose())
+				purple_debug_misc("http",
+					"Got main header with code %d\n",
+					hc->response->code);
+		} else {
+			if (purple_debug_is_verbose() &&
+				purple_debug_is_unsafe())
+				purple_debug_misc("http", "Got header: %s\n",
+					hdrline);
+			delim = strchr(hdrline, ':');
+			if (delim == NULL || delim == hdrline) {
+				purple_debug_warning("http",
+					"Bad header delimiter\n");
+				_purple_http_error(hc, _("Error parsing HTTP"));
+				return FALSE;
+			}
+			*delim++ = '\0';
+			while (*delim == ' ')
+				delim++;
+			
+			purple_http_headers_add(hc->response->headers, hdrline, delim);
+		}
+
+		g_string_erase(hc->response_buffer, 0, hdrline_len + 2);
+		if (hc->headers_got)
+			break;
+	}
+	return TRUE;
+}
+
+static void _purple_http_recv_body_data(PurpleHttpConnection *hc,
+	const gchar *buf, int len)
+{
+	if (hc->request->max_length >= 0) {
+		if (hc->data_length_got + len > hc->request->max_length) {
+			len = hc->request->max_length - hc->data_length_got;
+			hc->length_expected = hc->length_got;
+		}
+		hc->data_length_got += len;
+	}
+
+	purple_http_conn_notify_progress_watcher(hc);
+
+	if (len == 0)
+		return;
+
+	g_string_append_len(hc->response->contents, buf, len);
+}
+
+static gboolean _purple_http_recv_body_chunked(PurpleHttpConnection *hc,
+	const gchar *buf, int len)
+{
+	gchar *eol, *line;
+	int line_len;
+
+	if (hc->chunks_done)
+		return FALSE;
+	if (!hc->response_buffer)
+		hc->response_buffer = g_string_new("");
+
+	g_string_append_len(hc->response_buffer, buf, len);
+	if (hc->response_buffer->len > PURPLE_HTTP_MAX_RECV_BUFFER_LEN) {
+		purple_debug_error("http",
+			"Buffer too big when searching for chunk\n");
+		_purple_http_error(hc, _("Error parsing HTTP"));
+		return FALSE;
+	}
+
+	while (hc->response_buffer->len > 0) {
+		if (hc->in_chunk) {
+			int got_now = hc->response_buffer->len;
+			if (hc->chunk_got + got_now > hc->chunk_length)
+				got_now = hc->chunk_length - hc->chunk_got;
+			hc->chunk_got += got_now;
+			
+			_purple_http_recv_body_data(hc,
+				hc->response_buffer->str, got_now);
+
+			g_string_erase(hc->response_buffer, 0, got_now);
+			hc->in_chunk = (hc->chunk_got < hc->chunk_length);
+
+			if (purple_debug_is_verbose())
+				purple_debug_misc("http", "Chunk (%d/%d)\n",
+					hc->chunk_got, hc->chunk_length);
+
+			continue;
+		}
+
+		line = hc->response_buffer->str;
+		eol = strstr(line, "\r\n");
+		if (eol == line) {
+			g_string_erase(hc->response_buffer, 0, 2);
+			line = hc->response_buffer->str;
+			eol = strstr(line, "\r\n");
+		}
+		if (eol == NULL) {
+			/* waiting for more data (unlikely, but possible) */
+			if (hc->response_buffer->len > 20) {
+				purple_debug_warning("http", "Chunk length not "
+					"found (buffer too large)\n");
+				_purple_http_error(hc, _("Error parsing HTTP"));
+				return FALSE;
+			}
+			return TRUE;
+		}
+		line_len = eol - line;
+
+		if (1 != sscanf(line, "%x", &hc->chunk_length)) {
+			if (purple_debug_is_unsafe())
+				purple_debug_warning("http",
+					"Chunk length not found in [%s]\n",
+					line);
+			else
+				purple_debug_warning("http",
+					"Chunk length not found\n");
+			_purple_http_error(hc, _("Error parsing HTTP"));
+			return FALSE;
+		}
+		hc->chunk_got = 0;
+		hc->in_chunk = TRUE;
+
+		if (purple_debug_is_verbose())
+			purple_debug_misc("http", "Found chunk of length %d\n", hc->chunk_length);
+
+		g_string_erase(hc->response_buffer, 0, line_len + 2);
+
+		if (hc->chunk_length == 0) {
+			hc->chunks_done = TRUE;
+			hc->in_chunk = FALSE;
+			return TRUE;
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean _purple_http_recv_body(PurpleHttpConnection *hc,
+	const gchar *buf, int len)
+{
+	if (hc->response->contents == NULL)
+		hc->response->contents = g_string_new("");
+
+	if (hc->is_chunked)
+	{
+		hc->length_got += len;
+		return _purple_http_recv_body_chunked(hc, buf, len);
+	}
+
+	if (hc->length_expected >= 0 &&
+		len + hc->length_got > hc->length_expected)
+		len = hc->length_expected - hc->length_got;
+	hc->length_got += len;
+
+	_purple_http_recv_body_data(hc, buf, len);
+
+	return TRUE;
+}
+
+static void _purple_http_recv(gpointer _hc, gint fd, PurpleInputCondition cond)
+{
+	PurpleHttpConnection *hc = _hc;
+	PurpleHttpSocket *hs = &hc->socket;
+	int len;
+	gchar buf[4096];
+
+	if (hs->is_ssl)
+		len = purple_ssl_read(hs->ssl_connection, buf, sizeof(buf));
+	else
+		len = read(fd, buf, sizeof(buf));
+
+	if (len < 0 && errno == EAGAIN)
+		return;
+
+	if (len < 0) {
+		_purple_http_error(hc, _("Error reading from %s: %s"),
+			hc->url->host, g_strerror(errno));
+		return;
+	}
+
+	/* EOF */
+	if (len == 0) {
+		if (hc->length_expected >= 0 &&
+			hc->length_got < hc->length_expected) {
+			purple_debug_warning("http", "No more data while reading"
+				" contents\n");
+			_purple_http_error(hc, _("Error parsing HTTP"));
+			return;
+		}
+		if (hc->headers_got)
+			hc->length_expected = hc->length_got;
+		else {
+			purple_debug_warning("http", "No more data while "
+				"parsing headers\n");
+			_purple_http_error(hc, _("Error parsing HTTP"));
+			return;
+		}
+	}
+
+	if (!hc->headers_got && len > 0) {
+		if (!_purple_http_recv_headers(hc, buf, len))
+			return;
+		len = 0;
+		if (hc->headers_got) {
+			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"));
+		}
+		if (hc->headers_got && hc->response_buffer &&
+			hc->response_buffer->len > 0) {
+			int buffer_len = hc->response_buffer->len;
+			gchar *buffer = g_string_free(hc->response_buffer, FALSE);
+			hc->response_buffer = NULL;
+			_purple_http_recv_body(hc, buffer, buffer_len);
+		}
+		if (!hc->headers_got)
+			return;
+	}
+
+	if (len > 0) {
+		if (!_purple_http_recv_body(hc, buf, len))
+			return;
+	}
+
+	if (hc->is_chunked && hc->chunks_done)
+		hc->length_expected = hc->length_got;
+
+	if (hc->length_expected >= 0 && hc->length_got >= hc->length_expected) {
+		const gchar *redirect;
+
+		if (!hc->headers_got) {
+			hc->response->code = 0;
+			purple_debug_warning("http", "No headers got\n");
+			_purple_http_error(hc, _("Error parsing HTTP"));
+			return;
+		}
+
+		if (purple_debug_is_unsafe() && purple_debug_is_verbose()) {
+			gchar *hdrs = purple_http_headers_dump(
+				hc->response->headers);
+			purple_debug_misc("http", "Got response headers: %s\n",
+				hdrs);
+			g_free(hdrs);
+		}
+
+		purple_http_cookie_jar_parse(hc->request->cookie_jar,
+			purple_http_headers_get_all_by_name(
+				hc->response->headers, "Set-Cookie"));
+
+		if (purple_debug_is_unsafe() && purple_debug_is_verbose() &&
+			!purple_http_cookie_jar_is_empty(
+				hc->request->cookie_jar)) {
+			gchar *cookies = purple_http_cookie_jar_dump(
+				hc->request->cookie_jar);
+			purple_debug_misc("http", "Cookies: %s\n", cookies);
+			g_free(cookies);
+		}
+
+		if (hc->response->code == 407) {
+			_purple_http_error(hc, _("Invalid proxy credentials"));
+			return;
+		}
+
+		redirect = purple_http_headers_get(hc->response->headers,
+			"location");
+		if (redirect && (hc->request->max_redirects == -1 ||
+			hc->request->max_redirects > hc->redirects_count)) {
+			PurpleHttpURL *url = purple_http_url_parse(redirect);
+
+			hc->redirects_count++;
+
+			if (!url) {
+				if (purple_debug_is_unsafe())
+					purple_debug_warning("http",
+						"Invalid redirect to %s\n",
+						redirect);
+				else
+					purple_debug_warning("http",
+						"Invalid redirect\n");
+				_purple_http_error(hc, _("Error parsing HTTP"));
+			}
+
+			purple_http_url_relative(hc->url, url);
+			purple_http_url_free(url);
+
+			_purple_http_reconnect(hc);
+			return;
+		}
+
+		_purple_http_disconnect(hc);
+		purple_http_connection_terminate(hc);
+		return;
+	}
+}
+
+static void _purple_http_recv_ssl(gpointer _hc,
+	PurpleSslConnection *ssl_connection, PurpleInputCondition cond)
+{
+	_purple_http_recv(_hc, -1, cond);
+}
+
+static void _purple_http_send_got_data(PurpleHttpConnection *hc,
+	gboolean success, gboolean eof, size_t stored)
+{
+	int estimated_length;
+
+	g_return_if_fail(hc != NULL);
+
+	if (!success) {
+		_purple_http_error(hc, _("Error requesting data to write"));
+		return;
+	}
+
+	hc->contents_reader_requested = FALSE;
+	g_string_set_size(hc->contents_reader_buffer, stored);
+	if (!eof)
+		return;
+
+	estimated_length = hc->request_contents_written + stored;
+
+	if (hc->request->contents_length != -1 &&
+		hc->request->contents_length != estimated_length) {
+		purple_debug_warning("http",
+			"Invalid amount of data has been written\n");
+	}
+	hc->request->contents_length = estimated_length;
+}
+
+static void _purple_http_send(gpointer _hc, gint fd, PurpleInputCondition cond)
+{
+	PurpleHttpConnection *hc = _hc;
+	PurpleHttpSocket *hs = &hc->socket;
+	int written, write_len;
+	const gchar *write_from;
+	gboolean writing_headers;
+
+	/* Waiting for data. This could be written more efficiently, by removing
+	 * (and later, adding) hs->inpa. */
+	if (hc->contents_reader_requested)
+		return;
+
+	_purple_http_gen_headers(hc);
+
+	writing_headers =
+		(hc->request_header_written < hc->request_header->len);
+	if (writing_headers) {
+		write_from = hc->request_header->str +
+			hc->request_header_written;
+		write_len = hc->request_header->len -
+			hc->request_header_written;
+	} else if (hc->request->contents_reader) {
+		if (hc->contents_reader_requested)
+			return; /* waiting for data */
+		if (!hc->contents_reader_buffer)
+			hc->contents_reader_buffer = g_string_new("");
+		if (hc->contents_reader_buffer->len == 0) {
+			hc->contents_reader_requested = TRUE;
+			g_string_set_size(hc->contents_reader_buffer,
+				PURPLE_HTTP_MAX_READ_BUFFER_LEN);
+			hc->request->contents_reader(hc,
+				hc->contents_reader_buffer->str,
+				hc->request_contents_written,
+				PURPLE_HTTP_MAX_READ_BUFFER_LEN,
+				hc->request->contents_reader_data,
+				_purple_http_send_got_data);
+			return;
+		}
+		write_from = hc->contents_reader_buffer->str;
+		write_len = hc->contents_reader_buffer->len;
+	} else {
+		write_from = hc->request->contents +
+			hc->request_contents_written;
+		write_len = hc->request->contents_length -
+			hc->request_contents_written;
+	}
+
+	if (write_len == 0) {
+		purple_debug_warning("http", "Nothing to write\n");
+		written = 0;
+	} else if (hs->is_ssl)
+		written = purple_ssl_write(hs->ssl_connection,
+			write_from, write_len);
+	else
+		written = write(hs->fd, write_from, write_len);
+
+	if (written < 0 && errno == EAGAIN)
+		return;
+
+	if (written < 0) {
+		_purple_http_error(hc, _("Error writing to %s: %s"),
+			hc->url->host, g_strerror(errno));
+		return;
+	}
+
+	if (writing_headers) {
+		hc->request_header_written += written;
+		purple_http_conn_notify_progress_watcher(hc);
+		if (hc->request_header_written < hc->request_header->len)
+			return;
+		if (hc->request->contents_length > 0)
+			return;
+	} else {
+		hc->request_contents_written += written;
+		purple_http_conn_notify_progress_watcher(hc);
+		if (hc->contents_reader_buffer)
+			g_string_erase(hc->contents_reader_buffer, 0, written);
+		if (hc->request_contents_written < hc->request->contents_length)
+			return;
+	}
+
+	/* request is completely written, let's read the response */
+	hc->is_reading = TRUE;
+	purple_input_remove(hs->inpa);
+	hs->inpa = 0;
+	if (hs->is_ssl)
+		purple_ssl_input_add(hs->ssl_connection,
+			_purple_http_recv_ssl, hc);
+	else
+		hs->inpa = purple_input_add(hs->fd, PURPLE_INPUT_READ,
+			_purple_http_recv, hc);
+}
+
+static void _purple_http_connected_raw(gpointer _hc, gint fd,
+	const gchar *error_message)
+{
+	PurpleHttpConnection *hc = _hc;
+	PurpleHttpSocket *hs = &hc->socket;
+
+	hs->raw_connection = NULL;
+
+	if (fd == -1) {
+		_purple_http_error(hc, _("Unable to connect to %s: %s"),
+			hc->url->host, error_message);
+		return;
+	}
+
+	hs->fd = fd;
+	hs->inpa = purple_input_add(fd, PURPLE_INPUT_WRITE,
+		_purple_http_send, hc);
+}
+
+static void _purple_http_connected_ssl(gpointer _hc,
+	PurpleSslConnection *ssl_connection, PurpleInputCondition cond)
+{
+	PurpleHttpConnection *hc = _hc;
+	PurpleHttpSocket *hs = &hc->socket;
+
+	hs->fd = hs->ssl_connection->fd;
+	hs->inpa = purple_input_add(hs->fd, PURPLE_INPUT_WRITE,
+		_purple_http_send, hc);
+}
+
+static void _purple_http_connected_ssl_error(
+	PurpleSslConnection *ssl_connection, PurpleSslErrorType error,
+	gpointer _hc)
+{
+	PurpleHttpConnection *hc = _hc;
+	PurpleHttpSocket *hs = &hc->socket;
+
+	hs->ssl_connection = NULL;
+	_purple_http_error(hc, _("Unable to connect to %s: %s"),
+		hc->url->host, purple_ssl_strerror(error));
+}
+
+static void _purple_http_disconnect(PurpleHttpConnection *hc)
+{
+	PurpleHttpSocket *hs;
+
+	g_return_if_fail(hc != NULL);
+
+	hs = &hc->socket;
+
+	if (hc->request_header)
+		g_string_free(hc->request_header, TRUE);
+	hc->request_header = NULL;
+	if (hc->response_buffer)
+		g_string_free(hc->response_buffer, TRUE);
+	hc->response_buffer = NULL;
+
+	if (hs->inpa != 0)
+		purple_input_remove(hs->inpa);
+
+	if (hs->is_ssl) {
+		if (hs->ssl_connection != NULL)
+			purple_ssl_close(hs->ssl_connection);
+	} else {
+		if (hs->raw_connection != NULL)
+			purple_proxy_connect_cancel(hs->raw_connection);
+		if (hs->fd > 0)
+			close(hs->fd);
+	}
+
+	memset(hs, 0, sizeof(PurpleHttpSocket));
+}
+
+static gboolean _purple_http_reconnect(PurpleHttpConnection *hc)
+{
+	PurpleHttpURL *url;
+	gboolean is_ssl = FALSE;
+	PurpleAccount *account = NULL;
+
+	g_return_val_if_fail(hc != NULL, FALSE);
+	g_return_val_if_fail(hc->url != NULL, FALSE);
+
+	_purple_http_disconnect(hc);
+
+	if (purple_debug_is_verbose()) {
+		if (purple_debug_is_unsafe()) {
+			gchar *url = purple_http_url_print(hc->url);
+			purple_debug_misc("http", "Connecting to %s...\n", url);
+			g_free(url);
+		} else
+			purple_debug_misc("http", "Connecting to %s...\n",
+				hc->url->host);
+	}
+
+	if (hc->gc)
+		account = purple_connection_get_account(hc->gc);
+
+	url = hc->url;
+	if (url->protocol[0] == '\0' ||
+		g_ascii_strcasecmp(url->protocol, "http") == 0) {
+		/* do nothing */
+	} else if (g_ascii_strcasecmp(url->protocol, "https") == 0) {
+		is_ssl = TRUE;
+	} else {
+		_purple_http_error(hc, _("Unsupported protocol: %s"),
+			url->protocol);
+		return FALSE;
+	}
+
+	hc->socket.is_ssl = is_ssl;
+	if (is_ssl) {
+		if (!purple_ssl_is_supported()) {
+			_purple_http_error(hc, _("Unable to connect to %s: %s"),
+				url->host, _("Server requires TLS/SSL, "
+				"but no TLS/SSL support was found."));
+			return FALSE;
+		}
+		hc->socket.ssl_connection = purple_ssl_connect(account,
+			url->host, url->port,
+			_purple_http_connected_ssl,
+			_purple_http_connected_ssl_error, hc);
+/* TODO
+		purple_ssl_set_compatibility_level(hc->socket.ssl_connection,
+			PURPLE_SSL_COMPATIBILITY_SECURE);
+*/
+	} else {
+		hc->socket.raw_connection = purple_proxy_connect(hc->gc, account,
+			url->host, url->port,
+			_purple_http_connected_raw, hc);
+	}
+
+	if (hc->socket.ssl_connection == NULL &&
+		hc->socket.raw_connection == NULL) {
+		_purple_http_error(hc, _("Unable to connect to %s"), url->host);
+		return FALSE;
+	}
+
+	purple_http_headers_free(hc->response->headers);
+	hc->response->headers = purple_http_headers_new();
+	hc->response_buffer = g_string_new("");
+	hc->main_header_got = FALSE;
+	hc->headers_got = FALSE;
+	if (hc->response->contents != NULL)
+		g_string_free(hc->response->contents, TRUE);
+	hc->response->contents = NULL;
+	hc->length_got = 0;
+	hc->data_length_got = 0;
+	hc->length_expected = -1;
+	hc->is_chunked = FALSE;
+	hc->in_chunk = FALSE;
+	hc->chunks_done = FALSE;
+
+	purple_http_conn_notify_progress_watcher(hc);
+
+	return TRUE;
+}
+
+/*** Performing HTTP requests *************************************************/
+
+static gboolean purple_http_request_timeout(gpointer _hc)
+{
+	PurpleHttpConnection *hc = _hc;
+
+	purple_debug_warning("http", "Timeout reached for request %p\n", hc);
+
+	purple_http_conn_cancel(hc);
+
+	return FALSE;
+}
+
+PurpleHttpConnection * purple_http_get(PurpleConnection *gc, const gchar *url,
+	PurpleHttpCallback callback, gpointer user_data)
+{
+	PurpleHttpRequest *request;
+	PurpleHttpConnection *hc;
+
+	g_return_val_if_fail(url != NULL, NULL);
+
+	request = purple_http_request_new(url);
+	hc = purple_http_request(gc, request, callback, user_data);
+	purple_http_request_unref(request);
+
+	return hc;
+}
+
+PurpleHttpConnection * purple_http_request(PurpleConnection *gc,
+	PurpleHttpRequest *request, PurpleHttpCallback callback,
+	gpointer user_data)
+{
+	PurpleHttpConnection *hc;
+
+	g_return_val_if_fail(request != NULL, NULL);
+
+	hc = purple_http_connection_new(request, gc);
+	hc->callback = callback;
+	hc->user_data = user_data;
+
+	if (purple_debug_is_unsafe())
+		purple_debug_misc("http", "Performing new request %p for %s.\n",
+			hc, request->url);
+	else
+		purple_debug_misc("http", "Performing new request %p.\n", hc);
+
+	hc->url = purple_http_url_parse(request->url);
+	if (!hc->url || hc->url->host[0] == '\0') {
+		purple_debug_error("http", "Invalid URL requested.\n");
+		purple_http_connection_terminate(hc);
+		return NULL;
+	}
+
+	_purple_http_reconnect(hc);
+
+	hc->timeout_handle = purple_timeout_add_seconds(request->timeout,
+		purple_http_request_timeout, hc);
+
+	return hc;
+}
+
+/*** HTTP connection API ******************************************************/
+
+static void purple_http_connection_free(PurpleHttpConnection *hc);
+
+static PurpleHttpConnection * purple_http_connection_new(
+	PurpleHttpRequest *request, PurpleConnection *gc)
+{
+	PurpleHttpConnection *hc = g_new0(PurpleHttpConnection, 1);
+
+	hc->request = request;
+	purple_http_request_ref(request);
+	hc->response = purple_http_response_new();
+
+	hc->link_global = purple_http_hc_list =
+		g_list_prepend(purple_http_hc_list, hc);
+	g_hash_table_insert(purple_http_hc_by_ptr, hc, hc->link_global);
+	if (gc) {
+		GList *gc_list = g_hash_table_lookup(purple_http_hc_by_gc, gc);
+		g_hash_table_steal(purple_http_hc_by_gc, gc);
+		hc->link_gc = gc_list = g_list_prepend(gc_list, hc);
+		g_hash_table_insert(purple_http_hc_by_gc, gc, gc_list);
+		hc->gc = gc;
+	}
+
+	return hc;
+}
+
+static void purple_http_connection_free(PurpleHttpConnection *hc)
+{
+	if (hc->timeout_handle)
+		purple_timeout_remove(hc->timeout_handle);
+
+	purple_http_url_free(hc->url);
+	purple_http_request_unref(hc->request);
+	purple_http_response_free(hc->response);
+
+	if (hc->contents_reader_buffer)
+		g_string_free(hc->contents_reader_buffer, TRUE);
+
+	if (hc->request_header)
+		g_string_free(hc->request_header, TRUE);
+
+	purple_http_hc_list = g_list_delete_link(purple_http_hc_list,
+		hc->link_global);
+	g_hash_table_remove(purple_http_hc_by_ptr, hc);
+	if (hc->gc) {
+		GList *gc_list, *gc_list_new;
+		gc_list = g_hash_table_lookup(purple_http_hc_by_gc, hc->gc);
+		g_assert(gc_list != NULL);
+
+		gc_list_new = g_list_delete_link(gc_list, hc->link_gc);
+		if (gc_list != gc_list_new) {
+			g_hash_table_steal(purple_http_hc_by_gc, hc->gc);
+			if (gc_list_new)
+				g_hash_table_insert(purple_http_hc_by_gc,
+					hc->gc, gc_list_new);
+		}
+	}
+
+	g_free(hc);
+}
+
+/* call callback and do the cleanup */
+static void purple_http_connection_terminate(PurpleHttpConnection *hc)
+{
+	g_return_if_fail(hc != NULL);
+
+	purple_debug_misc("http", "Request %p performed %s.\n", hc,
+		purple_http_response_is_successfull(hc->response) ?
+		"successfully" : "without success");
+
+	if (hc->callback)
+		hc->callback(hc, hc->response, hc->user_data);
+
+	purple_http_connection_free(hc);
+}
+
+void purple_http_conn_cancel(PurpleHttpConnection *http_conn)
+{
+	if (http_conn == NULL)
+		return;
+
+	http_conn->response->code = 0;
+	_purple_http_disconnect(http_conn);
+	purple_http_connection_terminate(http_conn);
+}
+
+void purple_http_conn_cancel_all(PurpleConnection *gc)
+{
+	GList *gc_list = g_hash_table_lookup(purple_http_hc_by_gc, gc);
+
+	while (gc_list) {
+		PurpleHttpConnection *hc = gc_list->data;
+		gc_list = g_list_next(gc_list);
+		purple_http_conn_cancel(hc);
+	}
+
+	if (NULL != g_hash_table_lookup(purple_http_hc_by_gc, gc))
+		purple_debug_error("http", "Couldn't cancel all connections "
+			"related to gc=%p\n", gc);
+}
+
+gboolean purple_http_conn_is_running(PurpleHttpConnection *http_conn)
+{
+	if (http_conn == NULL)
+		return FALSE;
+	return (NULL != g_hash_table_lookup(purple_http_hc_by_ptr, http_conn));
+}
+
+PurpleHttpRequest * purple_http_conn_get_request(PurpleHttpConnection *http_conn)
+{
+	g_return_val_if_fail(http_conn != NULL, NULL);
+
+	return http_conn->request;
+}
+
+PurpleHttpCookieJar * purple_http_conn_get_cookie_jar(
+	PurpleHttpConnection *http_conn)
+{
+	return purple_http_request_get_cookie_jar(purple_http_conn_get_request(
+		http_conn));
+}
+
+PurpleConnection * purple_http_conn_get_purple_connection(
+	PurpleHttpConnection *http_conn)
+{
+	g_return_val_if_fail(http_conn != NULL, NULL);
+
+	return http_conn->gc;
+}
+
+void purple_http_conn_set_progress_watcher(PurpleHttpConnection *http_conn,
+	PurpleHttpProgressWatcher watcher, gpointer user_data,
+	guint interval_threshold)
+{
+	g_return_if_fail(http_conn != NULL);
+
+	http_conn->watcher = watcher;
+	http_conn->watcher_user_data = user_data;
+	http_conn->watcher_interval_threshold = interval_threshold;
+}
+
+static void purple_http_conn_notify_progress_watcher(
+	PurpleHttpConnection *hc)
+{
+	gint64 now;
+	gboolean reading_state;
+	int processed, total;
+
+	g_return_if_fail(hc != NULL);
+
+	if (hc->watcher == NULL)
+		return;
+
+	reading_state = hc->is_reading;
+	if (reading_state) {
+		total = hc->length_expected;
+		processed = hc->length_got;
+	} else {
+		total = hc->request->contents_length;
+		processed = hc->request_contents_written;
+		if (total == 0)
+			total = -1;
+	}
+	if (total != -1 && total < processed) {
+		purple_debug_warning("http", "Processed too much\n");
+		total = processed;
+	}
+
+	now = g_get_monotonic_time();
+	if (hc->watcher_last_call + hc->watcher_interval_threshold
+		> now && processed != total)
+		return;
+	hc->watcher_last_call = now;
+	hc->watcher(hc, reading_state, processed, total, hc->watcher_user_data);
+}
+
+/*** Cookie jar API ***********************************************************/
+
+static PurpleHttpCookie * purple_http_cookie_new(const gchar *value);
+void purple_http_cookie_free(PurpleHttpCookie *cookie);
+
+static void purple_http_cookie_jar_set_ext(PurpleHttpCookieJar *cookie_jar,
+	const gchar *name, const gchar *value, time_t expires);
+
+static PurpleHttpCookie * purple_http_cookie_new(const gchar *value)
+{
+	PurpleHttpCookie *cookie = g_new0(PurpleHttpCookie, 1);
+
+	cookie->value = g_strdup(value);
+	cookie->expires = -1;
+
+	return cookie;
+}
+
+void purple_http_cookie_free(PurpleHttpCookie *cookie)
+{
+	g_free(cookie->value);
+	g_free(cookie);
+}
+
+void purple_http_cookie_jar_free(PurpleHttpCookieJar *cookie_jar);
+
+PurpleHttpCookieJar * purple_http_cookie_jar_new(void)
+{
+	PurpleHttpCookieJar *cjar = g_new0(PurpleHttpCookieJar, 1);
+
+	cjar->ref_count = 1;
+	cjar->tab = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+		(GDestroyNotify)purple_http_cookie_free);
+
+	return cjar;
+}
+
+void purple_http_cookie_jar_free(PurpleHttpCookieJar *cookie_jar)
+{
+	g_hash_table_destroy(cookie_jar->tab);
+	g_free(cookie_jar);
+}
+
+void purple_http_cookie_jar_ref(PurpleHttpCookieJar *cookie_jar)
+{
+	g_return_if_fail(cookie_jar != NULL);
+
+	cookie_jar->ref_count++;
+}
+
+PurpleHttpCookieJar * purple_http_cookie_jar_unref(
+	PurpleHttpCookieJar *cookie_jar)
+{
+	if (cookie_jar == NULL)
+		return NULL;
+
+	g_return_val_if_fail(cookie_jar->ref_count > 0, NULL);
+
+	cookie_jar->ref_count--;
+	if (cookie_jar->ref_count > 0)
+		return cookie_jar;
+
+	purple_http_cookie_jar_free(cookie_jar);
+	return NULL;
+}
+
+static void purple_http_cookie_jar_parse(PurpleHttpCookieJar *cookie_jar,
+	GList *values)
+{
+	values = g_list_first(values);
+	while (values) {
+		const gchar *cookie = values->data;
+		const gchar *eqsign, *semicolon;
+		gchar *name, *value;
+		time_t expires = -1;
+		values = g_list_next(values);
+
+		eqsign = strchr(cookie, '=');
+		semicolon = strchr(cookie, ';');
+
+		if (eqsign == NULL || eqsign == cookie ||
+			(semicolon != NULL && semicolon < eqsign)) {
+			if (purple_debug_is_unsafe())
+				purple_debug_warning("http",
+					"Invalid cookie: [%s]\n", cookie);
+			else
+				purple_debug_warning("http", "Invalid cookie.");
+		}
+
+		name = g_strndup(cookie, eqsign - cookie);
+		eqsign++;
+		if (semicolon != NULL)
+			value = g_strndup(eqsign, semicolon - eqsign);
+		else
+			value = g_strdup(eqsign);
+
+		if (semicolon != NULL) {
+			GMatchInfo *match_info;
+			GRegex *re_expires = g_regex_new(
+				"expires=([a-z0-9, :]+)",
+				G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
+				G_REGEX_MATCH_NOTEMPTY, NULL);
+
+			g_regex_match(re_expires, semicolon, 0, &match_info);
+			if (g_match_info_matches(match_info)) {
+				gchar *expire_date =
+					g_match_info_fetch(match_info, 1);
+				expires = purple_http_rfc1123_to_time(
+					expire_date);
+				g_free(expire_date);
+			}
+			g_match_info_free(match_info);
+
+			g_regex_unref(re_expires);
+		}
+
+		purple_http_cookie_jar_set_ext(cookie_jar, name, value, expires);
+
+		g_free(name);
+		g_free(value);
+	}
+}
+
+static gchar * purple_http_cookie_jar_gen(PurpleHttpCookieJar *cookie_jar)
+{
+	GHashTableIter it;
+	gchar *key;
+	PurpleHttpCookie *cookie;
+	GString *str;
+	time_t now = time(NULL);
+
+	g_return_val_if_fail(cookie_jar != NULL, NULL);
+
+	str = g_string_new("");
+
+	g_hash_table_iter_init(&it, cookie_jar->tab);
+	while (g_hash_table_iter_next(&it, (gpointer*)&key,
+		(gpointer*)&cookie)) {
+		if (cookie->expires != -1 && cookie->expires <= now)
+			continue;
+		g_string_append_printf(str, "%s=%s; ", key, cookie->value);
+	}
+
+	if (str->len > 0)
+		g_string_truncate(str, str->len - 2);
+	return g_string_free(str, FALSE);
+}
+
+void purple_http_cookie_jar_set(PurpleHttpCookieJar *cookie_jar,
+	const gchar *name, const gchar *value)
+{
+	purple_http_cookie_jar_set_ext(cookie_jar, name, value, -1);
+}
+
+static void purple_http_cookie_jar_set_ext(PurpleHttpCookieJar *cookie_jar,
+	const gchar *name, const gchar *value, time_t expires)
+{
+	g_return_if_fail(cookie_jar != NULL);
+	g_return_if_fail(name != NULL);
+
+	if (expires != -1 && time(NULL) >= expires)
+		value = NULL;
+
+	if (value != NULL) {
+		PurpleHttpCookie *cookie = purple_http_cookie_new(value);
+		cookie->expires = expires;
+		g_hash_table_insert(cookie_jar->tab, g_strdup(name), cookie);
+	} else
+		g_hash_table_remove(cookie_jar->tab, name);
+}
+
+const gchar * purple_http_cookie_jar_get(PurpleHttpCookieJar *cookie_jar,
+	const gchar *name)
+{
+	PurpleHttpCookie *cookie;
+
+	g_return_val_if_fail(cookie_jar != NULL, NULL);
+	g_return_val_if_fail(name != NULL, NULL);
+
+	cookie = g_hash_table_lookup(cookie_jar->tab, name);
+	if (!cookie)
+		return NULL;
+
+	return cookie->value;
+}
+
+gchar * purple_http_cookie_jar_dump(PurpleHttpCookieJar *cjar)
+{
+	GHashTableIter it;
+	gchar *key;
+	PurpleHttpCookie *cookie;
+	GString *str = g_string_new("");
+
+	g_hash_table_iter_init(&it, cjar->tab);
+	while (g_hash_table_iter_next(&it, (gpointer*)&key, (gpointer*)&cookie))
+		g_string_append_printf(str, "%s: %s (expires: %lld)\n", key,
+			cookie->value, (long long int)cookie->expires);
+
+	if (str->len > 0)
+		g_string_truncate(str, str->len - 1);
+	return g_string_free(str, FALSE);
+}
+
+gboolean purple_http_cookie_jar_is_empty(PurpleHttpCookieJar *cookie_jar)
+{
+	g_return_val_if_fail(cookie_jar != NULL, TRUE);
+
+	return g_hash_table_size(cookie_jar->tab) == 0;
+}
+
+/*** Request API **************************************************************/
+
+static void purple_http_request_free(PurpleHttpRequest *request);
+
+PurpleHttpRequest * purple_http_request_new(const gchar *url)
+{
+	PurpleHttpRequest *request;
+
+	g_return_val_if_fail(url != NULL, NULL);
+
+	request = g_new0(PurpleHttpRequest, 1);
+
+	request->ref_count = 1;
+	request->url = g_strdup(url);
+	request->headers = purple_http_headers_new();
+	request->cookie_jar = purple_http_cookie_jar_new();
+
+	request->timeout = PURPLE_HTTP_REQUEST_DEFAULT_TIMEOUT;
+	request->max_redirects = PURPLE_HTTP_REQUEST_DEFAULT_MAX_REDIRECTS;
+	request->http11 = TRUE;
+	request->max_length = -1;
+
+	return request;
+}
+
+static void purple_http_request_free(PurpleHttpRequest *request)
+{
+	purple_http_headers_free(request->headers);
+	purple_http_cookie_jar_unref(request->cookie_jar);
+	g_free(request->url);
+	g_free(request);
+}
+
+void purple_http_request_ref(PurpleHttpRequest *request)
+{
+	g_return_if_fail(request != NULL);
+
+	request->ref_count++;
+}
+
+PurpleHttpRequest * purple_http_request_unref(PurpleHttpRequest *request)
+{
+	if (request == NULL)
+		return NULL;
+
+	g_return_val_if_fail(request->ref_count > 0, NULL);
+
+	request->ref_count--;
+	if (request->ref_count > 0)
+		return request;
+
+	purple_http_request_free(request);
+	return NULL;
+}
+
+void purple_http_request_set_url(PurpleHttpRequest *request, const gchar *url)
+{
+	g_return_if_fail(request != NULL);
+	g_return_if_fail(url != NULL);
+
+	g_free(request->url);
+	request->url = g_strdup(url);
+}
+
+const gchar * purple_http_request_get_url(PurpleHttpRequest *request)
+{
+	g_return_val_if_fail(request != NULL, NULL);
+
+	return request->url;
+}
+
+void purple_http_request_set_method(PurpleHttpRequest *request, const gchar *method)
+{
+	g_return_if_fail(request != NULL);
+
+	g_free(request->method);
+	request->method = g_strdup(method);
+}
+
+const gchar * purple_http_request_get_method(PurpleHttpRequest *request)
+{
+	g_return_val_if_fail(request != NULL, NULL);
+
+	return request->method;
+}
+
+void purple_http_request_set_contents(PurpleHttpRequest *request,
+	const gchar *contents, int length)
+{
+	g_return_if_fail(request != NULL);
+	g_return_if_fail(length >= -1);
+
+	request->contents_reader = NULL;
+	request->contents_reader_data = NULL;
+
+	g_free(request->contents);
+	if (contents == NULL || length == 0) {
+		request->contents = NULL;
+		request->contents_length = 0;
+		return;
+	}
+
+	if (length == -1)
+		length = strlen(contents);
+	request->contents = g_memdup(contents, length);
+	request->contents_length = length;
+}
+
+void purple_http_request_set_contents_reader(PurpleHttpRequest *request,
+	PurpleHttpContentReader reader, int contents_length, gpointer user_data)
+{
+	g_return_if_fail(request != NULL);
+	g_return_if_fail(reader != NULL);
+	g_return_if_fail(contents_length >= -1);
+
+	g_free(request->contents);
+	request->contents = NULL;
+	request->contents_length = contents_length;
+	request->contents_reader = reader;
+	request->contents_reader_data = user_data;
+}
+
+void purple_http_request_set_timeout(PurpleHttpRequest *request, int timeout)
+{
+	g_return_if_fail(request != NULL);
+
+	if (timeout < -1)
+		timeout = -1;
+
+	request->timeout = timeout;
+}
+
+int purple_http_request_get_timeout(PurpleHttpRequest *request)
+{
+	g_return_val_if_fail(request != NULL, -1);
+
+	return request->timeout;
+}
+
+void purple_http_request_set_max_redirects(PurpleHttpRequest *request,
+	int max_redirects)
+{
+	g_return_if_fail(request != NULL);
+
+	if (max_redirects < -1)
+		max_redirects = -1;
+
+	request->max_redirects = max_redirects;
+}
+
+int purple_http_request_get_max_redirects(PurpleHttpRequest *request)
+{
+	g_return_val_if_fail(request != NULL, -1);
+
+	return request->max_redirects;
+}
+
+void purple_http_request_set_cookie_jar(PurpleHttpRequest *request,
+	PurpleHttpCookieJar *cookie_jar)
+{
+	g_return_if_fail(request != NULL);
+	g_return_if_fail(cookie_jar != NULL);
+
+	purple_http_cookie_jar_ref(cookie_jar);
+	purple_http_cookie_jar_unref(request->cookie_jar);
+	request->cookie_jar = cookie_jar;
+}
+
+PurpleHttpCookieJar * purple_http_request_get_cookie_jar(
+	PurpleHttpRequest *request)
+{
+	g_return_val_if_fail(request != NULL, NULL);
+
+	return request->cookie_jar;
+}
+
+void purple_http_request_set_http11(PurpleHttpRequest *request, gboolean http11)
+{
+	g_return_if_fail(request != NULL);
+
+	request->http11 = http11;
+}
+
+gboolean purple_http_request_is_http11(PurpleHttpRequest *request)
+{
+	g_return_val_if_fail(request != NULL, FALSE);
+
+	return request->http11;
+}
+
+void purple_http_request_set_max_len(PurpleHttpRequest *request, int max_len)
+{
+	g_return_if_fail(request != NULL);
+
+	if (max_len < -1)
+		max_len = -1;
+
+	request->max_length = max_len;
+}
+
+int purple_http_request_get_max_len(PurpleHttpRequest *request)
+{
+	g_return_val_if_fail(request != NULL, -1);
+
+	return request->max_length;
+}
+
+void purple_http_request_header_set(PurpleHttpRequest *request,
+	const gchar *key, const gchar *value)
+{
+	g_return_if_fail(request != NULL);
+	g_return_if_fail(key != NULL);
+
+	purple_http_headers_remove(request->headers, key);
+	if (value)
+		purple_http_headers_add(request->headers, key, value);
+}
+
+void purple_http_request_header_set_printf(PurpleHttpRequest *request,
+	const gchar *key, const gchar *format, ...)
+{
+	va_list args;
+	gchar *value;
+
+	g_return_if_fail(request != NULL);
+	g_return_if_fail(key != NULL);
+	g_return_if_fail(format != NULL);
+
+	va_start(args, format);
+	value = g_strdup_vprintf(format, args);
+	va_end(args);
+
+	purple_http_request_header_set(request, key, value);
+	g_free(value);
+}
+
+void purple_http_request_header_add(PurpleHttpRequest *request,
+	const gchar *key, const gchar *value)
+{
+	g_return_if_fail(request != NULL);
+	g_return_if_fail(key != NULL);
+
+	purple_http_headers_add(request->headers, key, value);
+}
+
+/*** HTTP response API ********************************************************/
+
+static PurpleHttpResponse * purple_http_response_new(void)
+{
+	PurpleHttpResponse *response = g_new0(PurpleHttpResponse, 1);
+
+	return response;
+}
+
+static void purple_http_response_free(PurpleHttpResponse *response)
+{
+	if (response->contents != NULL)
+		g_string_free(response->contents, TRUE);
+	g_free(response->error);
+	purple_http_headers_free(response->headers);
+	g_free(response);
+}
+
+gboolean purple_http_response_is_successfull(PurpleHttpResponse *response)
+{
+	int code;
+
+	g_return_val_if_fail(response != NULL, FALSE);
+
+	code = response->code;
+
+	if (code <= 0)
+		return FALSE;
+
+	if (code / 100 == 2)
+		return TRUE;
+
+	return FALSE;
+}
+
+int purple_http_response_get_code(PurpleHttpResponse *response)
+{
+	g_return_val_if_fail(response != NULL, 0);
+
+	return response->code;
+}
+
+const gchar * purple_http_response_get_error(PurpleHttpResponse *response)
+{
+	g_return_val_if_fail(response != NULL, NULL);
+
+	return response->error;
+}
+
+gsize purple_http_response_get_data_len(PurpleHttpResponse *response)
+{
+	g_return_val_if_fail(response != NULL, 0);
+
+	if (response->contents == NULL)
+		return 0;
+
+	return response->contents->len;
+}
+
+const gchar * purple_http_response_get_data(PurpleHttpResponse *response)
+{
+	g_return_val_if_fail(response != NULL, NULL);
+
+	if (response->contents == NULL)
+		return "";
+
+	return response->contents->str;
+}
+
+const GList * purple_http_response_get_all_headers(PurpleHttpResponse *response)
+{
+	g_return_val_if_fail(response != NULL, NULL);
+
+	return purple_http_headers_get_all(response->headers);
+}
+
+const GList * purple_http_response_get_headers_by_name(
+	PurpleHttpResponse *response, const gchar *name)
+{
+	g_return_val_if_fail(response != NULL, NULL);
+	g_return_val_if_fail(name != NULL, NULL);
+
+	return purple_http_headers_get_all_by_name(response->headers, name);
+}
+
+const gchar * purple_http_response_get_header(PurpleHttpResponse *response,
+	const gchar *name)
+{
+	g_return_val_if_fail(response != NULL, NULL);
+	g_return_val_if_fail(name != NULL, NULL);
+
+	return purple_http_headers_get(response->headers, name);
+}
+
+/*** URL functions ************************************************************/
+
+static PurpleHttpURL * purple_http_url_parse(const char *raw_url)
+{
+	PurpleHttpURL *url;
+	GMatchInfo *match_info;
+
+	gchar *host_full, *tmp;
+
+	g_return_val_if_fail(raw_url != NULL, NULL);
+
+	url = g_new0(PurpleHttpURL, 1);
+
+	if (!g_regex_match(purple_http_re_url, raw_url, 0, &match_info)) {
+		if (purple_debug_is_verbose() && purple_debug_is_unsafe()) {
+			purple_debug_warning("http",
+				"Invalid URL provided: %s\n",
+				raw_url);
+		}
+		return NULL;
+	}
+
+	url->protocol = g_match_info_fetch(match_info, 1);
+	host_full = g_match_info_fetch(match_info, 2);
+	url->path = g_match_info_fetch(match_info, 3);
+	url->fragment = g_match_info_fetch(match_info, 4);
+	g_match_info_free(match_info);
+
+	if (url->protocol[0] == '\0') {
+		g_free(url->protocol);
+		url->protocol = NULL;
+	} else if (url->protocol != NULL) {
+		tmp = url->protocol;
+		url->protocol = g_ascii_strdown(url->protocol, -1);
+		g_free(tmp);
+	}
+	if (host_full[0] == '\0') {
+		g_free(host_full);
+		host_full = NULL;
+	}
+	if (url->path[0] == '\0') {
+		g_free(url->path);
+		url->path = NULL;
+	}
+	if ((url->protocol == NULL) != (host_full == NULL))
+		purple_debug_warning("http", "Protocol or host not present "
+			"(unlikely case)\n");
+
+	if (host_full) {
+		gchar *port_str;
+
+		if (!g_regex_match(purple_http_re_url_host, host_full, 0,
+			&match_info)) {
+			if (purple_debug_is_verbose() &&
+				purple_debug_is_unsafe()) {
+				purple_debug_warning("http",
+					"Invalid host provided for URL: %s\n",
+					raw_url);
+			}
+
+			g_free(host_full);
+			purple_http_url_free(url);
+			return NULL;
+		}
+
+		url->user = g_match_info_fetch(match_info, 1);
+		url->password = g_match_info_fetch(match_info, 2);
+		url->host = g_match_info_fetch(match_info, 3);
+		port_str = g_match_info_fetch(match_info, 4);
+
+		if (port_str && port_str[0])
+			url->port = atoi(port_str);
+
+		if (url->user[0] == '\0') {
+			g_free(url->user);
+			url->user = NULL;
+		}
+		if (url->password[0] == '\0') {
+			g_free(url->password);
+			url->password = NULL;
+		}
+		if (url->host[0] == '\0') {
+			g_free(url->host);
+			url->host = NULL;
+		} else if (url->host != NULL) {
+			tmp = url->host;
+			url->host = g_ascii_strdown(url->host, -1);
+			g_free(tmp);
+		}
+
+		g_free(port_str);
+		g_match_info_free(match_info);
+
+		g_free(host_full);
+		host_full = NULL;
+	}
+
+	if (url->host != NULL) {
+		if (url->protocol == NULL)
+			url->protocol = g_strdup("http");
+		if (url->port == 0 && 0 == strcmp(url->protocol, "http"))
+			url->port = 80;
+		if (url->port == 0 && 0 == strcmp(url->protocol, "https"))
+			url->port = 443;
+		if (url->path == NULL)
+			url->path = g_strdup("/");
+		if (url->path[0] != '/')
+			purple_debug_warning("http",
+				"URL path doesn't start with slash\n");
+	}
+
+	return url;
+}
+
+static void purple_http_url_free(PurpleHttpURL *parsed_url)
+{
+	if (parsed_url == NULL)
+		return;
+
+	g_free(parsed_url->protocol);
+	g_free(parsed_url->user);
+	g_free(parsed_url->password);
+	g_free(parsed_url->host);
+	g_free(parsed_url->path);
+	g_free(parsed_url->fragment);
+	g_free(parsed_url);
+}
+
+static void purple_http_url_relative(PurpleHttpURL *base_url,
+	PurpleHttpURL *relative_url)
+{
+	g_return_if_fail(base_url != NULL);
+	g_return_if_fail(relative_url != NULL);
+
+	if (relative_url->host) {
+		g_free(base_url->protocol);
+		base_url->protocol = g_strdup(relative_url->protocol);
+		g_free(base_url->user);
+		base_url->user = g_strdup(relative_url->user);
+		g_free(base_url->password);
+		base_url->password = g_strdup(relative_url->password);
+		g_free(base_url->host);
+		base_url->host = g_strdup(relative_url->host);
+		base_url->port = relative_url->port;
+
+		g_free(base_url->path);
+		base_url->path = NULL;
+	}
+
+	if (relative_url->path) {
+		if (relative_url->path[0] == '/' ||
+			base_url->path == NULL) {
+			g_free(base_url->path);
+			base_url->path = g_strdup(relative_url->path);
+		} else {
+			gchar *last_slash = strrchr(base_url->path, '/');
+			gchar *tmp;
+			if (last_slash == NULL)
+				base_url->path[0] = '\0';
+			else
+				last_slash[1] = '\0';
+			tmp = base_url->path;
+			base_url->path = g_strconcat(base_url->path,
+				relative_url->path, NULL);
+			g_free(tmp);
+		}
+	}
+
+	g_free(base_url->fragment);
+	base_url->fragment = g_strdup(relative_url->fragment);
+}
+
+static gchar * purple_http_url_print(PurpleHttpURL *parsed_url)
+{
+	GString *url = g_string_new("");
+	gboolean before_host_printed = FALSE, host_printed = FALSE;
+	gboolean port_is_default = FALSE;
+
+	if (parsed_url->protocol) {
+		g_string_append_printf(url, "%s://", parsed_url->protocol);
+		before_host_printed = TRUE;
+		if (parsed_url->port == 80 && 0 == strcmp(parsed_url->protocol,
+			"http"))
+			port_is_default = TRUE;
+		if (parsed_url->port == 443 && 0 == strcmp(parsed_url->protocol,
+			"https"))
+			port_is_default = TRUE;
+	}
+	if (parsed_url->user || parsed_url->password) {
+		if (parsed_url->user)
+			g_string_append(url, parsed_url->user);
+		g_string_append_printf(url, ":%s", parsed_url->password);
+		g_string_append(url, "@");
+		before_host_printed = TRUE;
+	}
+	if (parsed_url->host || parsed_url->port) {
+		if (!parsed_url->host)
+			g_string_append_printf(url, "{???}:%d",
+				parsed_url->port);
+		else {
+			g_string_append(url, parsed_url->host);
+			if (!port_is_default)
+				g_string_append_printf(url, ":%d",
+					parsed_url->port);
+		}
+		host_printed = TRUE;
+	}
+	if (parsed_url->path) {
+		if (!host_printed && before_host_printed)
+			g_string_append(url, "{???}");
+		g_string_append(url, parsed_url->path);
+	}
+	if (parsed_url->fragment)
+		g_string_append_printf(url, "#%s", parsed_url->fragment);
+
+	return g_string_free(url, FALSE);
+}
+
+/*** HTTP Subsystem ***********************************************************/
+
+void purple_http_init(void)
+{
+	purple_http_re_url = g_regex_new("^"
+
+		"(?:" /* host part beginning */
+		"([a-z]+)\\:/*" /* protocol */
+		"([^/]+)" /* username, password, host, port */
+		")?" /* host part ending */
+
+		"([^#]*)" /* path */
+
+		"(?:#" "(.*)" ")?" /* fragment */
+
+		"$", G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
+		G_REGEX_MATCH_NOTEMPTY, NULL);
+
+	purple_http_re_url_host = g_regex_new("^"
+
+		"(?:" /* user credentials part beginning */
+		"([" PURPLE_HTTP_URL_CREDENTIALS_CHARS "]+)" /* username */
+		"(?::([" PURPLE_HTTP_URL_CREDENTIALS_CHARS "]+))" /* password */
+		"@)?" /* user credentials part ending */
+
+		"([a-z0-9.-]+)" /* host */
+		"(?::([0-9]+))?" /* port*/
+
+		"$", G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
+		G_REGEX_MATCH_NOTEMPTY, NULL);
+
+	purple_http_re_rfc1123 = g_regex_new(
+		"^[a-z]+, " /* weekday */
+		"([0-9]+) " /* date */
+		"([a-z]+) " /* month */
+		"([0-9]+) " /* year */
+		"([0-9]+:[0-9]+:[0-9]+) " /* time */
+		"(?:GMT|UTC)$",
+		G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
+		G_REGEX_MATCH_NOTEMPTY, NULL);
+
+	purple_http_hc_list = NULL;
+	purple_http_hc_by_ptr = g_hash_table_new(g_direct_hash, g_direct_equal);
+	purple_http_hc_by_gc = g_hash_table_new_full(g_direct_hash,
+		g_direct_equal, NULL, (GDestroyNotify)g_list_free);
+}
+
+static void purple_http_foreach_conn_cancel(gpointer _hc, gpointer user_data)
+{
+	PurpleHttpConnection *hc = _hc;
+	purple_http_conn_cancel(hc);
+}
+
+void purple_http_uninit(void)
+{
+	g_regex_unref(purple_http_re_url);
+	purple_http_re_url = NULL;
+	g_regex_unref(purple_http_re_url_host);
+	purple_http_re_url_host = NULL;
+	g_regex_unref(purple_http_re_rfc1123);
+	purple_http_re_rfc1123 = NULL;
+
+	g_list_foreach(purple_http_hc_list, purple_http_foreach_conn_cancel,
+		NULL);
+
+	if (purple_http_hc_list != NULL ||
+		0 != g_hash_table_size(purple_http_hc_by_ptr) ||
+		0 != g_hash_table_size(purple_http_hc_by_gc))
+		purple_debug_warning("http",
+			"Couldn't cleanup all connections.\n");
+
+	g_list_free(purple_http_hc_list);
+	purple_http_hc_list = NULL;
+	g_hash_table_destroy(purple_http_hc_by_gc);
+	purple_http_hc_by_gc = NULL;
+	g_hash_table_destroy(purple_http_hc_by_ptr);
+	purple_http_hc_by_ptr = NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/http.h	Mon Nov 05 18:15:24 2012 -0500
@@ -0,0 +1,589 @@
+/**
+ * @file http.h HTTP API
+ * @ingroup core
+ */
+
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _PURPLE_HTTP_H_
+#define _PURPLE_HTTP_H_
+
+#include <glib.h>
+
+#include "connection.h"
+
+/**
+ * A structure containing all data required to generate a single HTTP request.
+ */
+typedef struct _PurpleHttpRequest PurpleHttpRequest;
+
+/**
+ * A representation of actually running HTTP request. Can be used to cancel the
+ * request.
+ */
+typedef struct _PurpleHttpConnection PurpleHttpConnection;
+
+/**
+ * All information got with response for HTTP request.
+ */
+typedef struct _PurpleHttpResponse PurpleHttpResponse;
+
+/**
+ * An collection of cookies, got from HTTP response or provided for HTTP
+ * request.
+ */
+typedef struct _PurpleHttpCookieJar PurpleHttpCookieJar;
+
+/**
+ * An callback called after performing (successfully or not) HTTP request.
+ */
+typedef void (*PurpleHttpCallback)(PurpleHttpConnection *http_conn,
+	PurpleHttpResponse *response, gpointer user_data);
+
+/**
+ * An callback called after storing data requested by PurpleHttpContentReader.
+ */
+typedef void (*PurpleHttpContentReaderCb)(PurpleHttpConnection *http_conn,
+	gboolean success, gboolean eof, size_t stored);
+
+/**
+ * An callback for getting large request contents (ie. from file stored on
+ * disk).
+ *
+ * @param http_conn Connection, which requests data.
+ * @param buffer    Buffer to store data to (with offset ignored).
+ * @param offset    Position, from where to read data.
+ * @param length    Length of data to read.
+ * @param user_data The user data passed with callback function.
+ * @param cb        The function to call after storing data to buffer.
+ */
+typedef void (*PurpleHttpContentReader)(PurpleHttpConnection *http_conn,
+	gchar *buffer, size_t offset, size_t length, gpointer user_data,
+	PurpleHttpContentReaderCb cb);
+
+/**
+ * An callback for writting large response contents.
+ *
+ * @param http_conn Connection, which requests data.
+ * @param response  Response at point got so far (may change later).
+ * @param buffer    Buffer to read data from (with offset ignored).
+ * @param offset    Position of data got (its value is offset + length of
+ *                  previous call), can be safely ignored.
+ * @param length    Length of data read.
+ * @param user_data The user data passed with callback function.
+ */
+typedef void (*PurpleHttpContentWriter)(PurpleHttpConnection *http_conn,
+	PurpleHttpResponse *response, const gchar *buffer, size_t offset,
+	size_t length, gpointer user_data);
+
+/**
+ * An callback for watching HTTP connection progress.
+ *
+ * @param http_conn     The HTTP Connection.
+ * @param reading_state FALSE, is we are sending the request, TRUE, when reading
+ *                      the response.
+ * @param processed     The amount of data already processed.
+ * @param total         Total amount of data (in current state).
+ * @param user_data     The user data passed with callback function.
+ */
+typedef void (*PurpleHttpProgressWatcher)(PurpleHttpConnection *http_conn,
+	gboolean reading_state, int processed, int total, gpointer user_data);
+
+G_BEGIN_DECLS
+
+/**************************************************************************/
+/** @name Performing HTTP requests                                        */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Fetches the data from a URL with GET request, and passes it to a callback
+ * function.
+ *
+ * @param gc       The connection for which the request is needed, or NULL.
+ * @param url      The URL.
+ * @param callback The callback function.
+ * @param data     The user data to pass to the callback function.
+ * @return         The HTTP connection struct.
+ */
+PurpleHttpConnection * purple_http_get(PurpleConnection *gc, const gchar *url,
+	PurpleHttpCallback callback, gpointer user_data);
+
+/**
+ * Fetches a HTTP request and passes the response to a callback function.
+ * Provided request struct can be shared by multiple http requests but can not
+ * be modified when any of these is running.
+ *
+ * @param gc        The connection for which the request is needed, or NULL.
+ * @param request   The request.
+ * @param callback  The callback function.
+ * @param user_data The user data to pass to the callback function.
+ * @return          The HTTP connection struct.
+ */
+PurpleHttpConnection * purple_http_request(PurpleConnection *gc,
+	PurpleHttpRequest *request, PurpleHttpCallback callback,
+	gpointer user_data);
+
+/**************************************************************************/
+/** @name HTTP connection API                                             */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Cancel a pending HTTP request.
+ *
+ * @param http_conn The data returned when you initiated the HTTP request.
+ */
+void purple_http_conn_cancel(PurpleHttpConnection *http_conn);
+
+/**
+ * Cancels all HTTP connections associated with the specified handle.
+ *
+ * @param gc The handle.
+ */
+void purple_http_conn_cancel_all(PurpleConnection *gc);
+
+/**
+ * Checks, if provided HTTP request is running.
+ *
+ * @param http_conn The HTTP connection (may be invalid pointer).
+ * @return          TRUE, if provided connection is currently running.
+ */
+gboolean purple_http_conn_is_running(PurpleHttpConnection *http_conn);
+
+/**
+ * Gets PurpleHttpRequest used for specified HTTP connection.
+ *
+ * @param http_conn The HTTP connection.
+ * @return          The PurpleHttpRequest object.
+ */
+PurpleHttpRequest * purple_http_conn_get_request(
+	PurpleHttpConnection *http_conn);
+
+/**
+ * Gets cookie jar used within connection.
+ *
+ * @param http_conn The HTTP connection.
+ * @return          The cookie jar.
+ */
+PurpleHttpCookieJar * purple_http_conn_get_cookie_jar(
+	PurpleHttpConnection *http_conn);
+
+/**
+ * Gets PurpleConnection tied with specified HTTP connection.
+ *
+ * @param http_conn The HTTP connection.
+ * @return          The PurpleConnection object.
+ */
+PurpleConnection * purple_http_conn_get_purple_connection(
+	PurpleHttpConnection *http_conn);
+
+/**
+ * Sets the watcher, called after writing or reading data to/from HTTP stream.
+ * May be used for updating transfer progress gauge.
+ *
+ * @param http_conn          The HTTP connection.
+ * @param watcher            The watcher.
+ * @param user_data          The user data to pass to the callback function.
+ * @param interval_threshold Minimum interval (in microseconds) of calls to
+ *                           watcher.
+ */
+void purple_http_conn_set_progress_watcher(PurpleHttpConnection *http_conn,
+	PurpleHttpProgressWatcher watcher, gpointer user_data,
+	guint interval_threshold);
+
+/*@}*/
+
+
+/**************************************************************************/
+/** @name Cookie jar API                                                  */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates new cookie jar,
+ *
+ * @return empty cookie jar.
+ */
+PurpleHttpCookieJar * purple_http_cookie_jar_new(void);
+
+/**
+ * Increment the reference count.
+ *
+ * @param cookie_jar The cookie jar.
+ */
+void purple_http_cookie_jar_ref(PurpleHttpCookieJar *cookie_jar);
+
+/**
+ * Decrement the reference count.
+ *
+ * If the reference count reaches zero, the cookie jar will be freed.
+ *
+ * @param cookie_jar The cookie jar.
+ * @return @a cookie_jar or @c NULL if the reference count reached zero.
+ */
+PurpleHttpCookieJar * purple_http_cookie_jar_unref(
+	PurpleHttpCookieJar *cookie_jar);
+
+/**
+ * Sets the cookie.
+ *
+ * @param cookie_jar The cookie jar.
+ * @param name       Cookie name.
+ * @param value      Cookie contents.
+ */
+void purple_http_cookie_jar_set(PurpleHttpCookieJar *cookie_jar,
+	const gchar *name, const gchar *value);
+
+/**
+ * Gets the cookie.
+ *
+ * @param cookie_jar The cookie jar.
+ * @param name       Cookie name.
+ * @return           Cookie contents, or NULL, if cookie doesn't exists.
+ */
+const gchar * purple_http_cookie_jar_get(PurpleHttpCookieJar *cookie_jar,
+	const gchar *name);
+
+/**
+ * Checks, if the cookie jar contains any cookies.
+ *
+ * @param cookie_jar The cookie jar.
+ * @return           TRUE, if cookie jar contains any cookie, FALSE otherwise.
+ */
+gboolean purple_http_cookie_jar_is_empty(PurpleHttpCookieJar *cookie_jar);
+
+/*@}*/
+
+
+/**************************************************************************/
+/** @name HTTP Request API                                                */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates the new instance of HTTP request configuration.
+ *
+ * @param url The URL to request for.
+ * @return The new instance of HTTP request struct.
+ */
+PurpleHttpRequest * purple_http_request_new(const gchar *url);
+
+/**
+ * Increment the reference count.
+ *
+ * @param request The request.
+ */
+void purple_http_request_ref(PurpleHttpRequest *request);
+
+/**
+ * Decrement the reference count.
+ *
+ * If the reference count reaches zero, the http request struct will be freed.
+ *
+ * @param request The request.
+ * @return @a request or @c NULL if the reference count reached zero.
+ */
+PurpleHttpRequest * purple_http_request_unref(PurpleHttpRequest *request);
+
+/**
+ * Sets URL for HTTP request.
+ *
+ * @param request The request.
+ * @param url     The url.
+ */
+void purple_http_request_set_url(PurpleHttpRequest *request, const gchar *url);
+
+/**
+ * Gets URL set for the HTTP request.
+ *
+ * @param request The request.
+ * @return        URL set for this request.
+ */
+const gchar * purple_http_request_get_url(PurpleHttpRequest *request);
+
+/**
+ * Sets custom HTTP method used for the request.
+ *
+ * @param request The request.
+ * @param method  The method, or NULL for default.
+ */
+void purple_http_request_set_method(PurpleHttpRequest *request,
+	const gchar *method);
+
+/**
+ * Gets HTTP method set for the request.
+ *
+ * @param request The request.
+ * @return        The method.
+ */
+const gchar * purple_http_request_get_method(PurpleHttpRequest *request);
+
+/**
+ * Sets contents of HTTP request (for example, POST data).
+ *
+ * @param request  The request.
+ * @param contents The contents.
+ * @param length   The length of contents (-1 if it's a NULL-terminated string)
+ */
+void purple_http_request_set_contents(PurpleHttpRequest *request,
+	const gchar *contents, int length);
+
+/**
+ * Sets contents reader for HTTP request, used mainly for possible large
+ * uploads.
+ *
+ * @param request       The request.
+ * @param reader        The reader callback.
+ * @param contents_size The size of all contents.
+ * @param user_data     The user data to pass to the callback function.
+ */
+void purple_http_request_set_contents_reader(PurpleHttpRequest *request,
+	PurpleHttpContentReader reader, int contents_length, gpointer user_data);
+
+/**
+ * Set contents writer for HTTP response.
+ *
+ * @param request   The request.
+ * @param reader    The writer callback.
+ * @param user_data The user data to pass to the callback function.
+ */
+/* TODO */
+void purple_http_request_set_response_writer(PurpleHttpRequest *request,
+	PurpleHttpContentWriter writer, gpointer user_data);
+
+/**
+ * Set maximum amount of time, that request is allowed to run.
+ *
+ * @param request The request.
+ * @param timeout Time (in seconds) after that timeout will be cancelled,
+ *                -1 for infinite time.
+ */
+void purple_http_request_set_timeout(PurpleHttpRequest *request, int timeout);
+
+/**
+ * Get maximum amount of time, that request is allowed to run.
+ *
+ * @param request The request.
+ * @return        Timeout currently set (-1 for infinite).
+ */
+int purple_http_request_get_timeout(PurpleHttpRequest *request);
+
+/**
+ * Sets maximum amount of redirects.
+ *
+ * @param request       The request.
+ * @param max_redirects Maximum amount of redirects, or -1 for unlimited.
+ */
+void purple_http_request_set_max_redirects(PurpleHttpRequest *request,
+	int max_redirects);
+
+/**
+ * Gets maximum amount of redirects.
+ *
+ * @param request The request.
+ * @return        Current maximum amount of redirects (-1 for unlimited).
+ */
+int purple_http_request_get_max_redirects(PurpleHttpRequest *request);
+
+/**
+ * Sets cookie jar used for the request.
+ *
+ * @param request    The request.
+ * @param cookie_jar The cookie jar.
+ */
+void purple_http_request_set_cookie_jar(PurpleHttpRequest *request,
+	PurpleHttpCookieJar *cookie_jar);
+
+/**
+ * Gets cookie jar used for the request.
+ *
+ * @param request The request.
+ * @return        The cookie jar.
+ */
+PurpleHttpCookieJar * purple_http_request_get_cookie_jar(
+	PurpleHttpRequest *request);
+
+/**
+ * Sets HTTP version to use.
+ *
+ * @param request The request.
+ * @param http11  TRUE for HTTP/1.1, FALSE for HTTP/1.0.
+ */
+void purple_http_request_set_http11(PurpleHttpRequest *request,
+	gboolean http11);
+
+/**
+ * Gets used HTTP version.
+ *
+ * @param request The request.
+ * @return        TRUE, if we use HTTP/1.1, FALSE for HTTP/1.0.
+ */
+gboolean purple_http_request_is_http11(PurpleHttpRequest *request);
+
+/**
+ * Sets maximum length of response content to read.
+ *
+ * Headers length doesn't count here.
+ *
+ * @param request The request.
+ * @param max_len Maximum length of response to read (-1 for unlimited).
+ */
+void purple_http_request_set_max_len(PurpleHttpRequest *request, int max_len);
+
+/**
+ * Gets maximum length of response content to read.
+ *
+ * @param request The request.
+ * @return        Maximum length of response to read, or -1 if unlimited.
+ */
+int purple_http_request_get_max_len(PurpleHttpRequest *request);
+
+/**
+ * Sets (replaces, if exists) specified HTTP request header with provided value.
+ *
+ * @param key   A header to be set.
+ * @param value A value to set, or NULL to remove specified header from request.
+ *
+ * @see purple_http_request_header_add
+ */
+void purple_http_request_header_set(PurpleHttpRequest *request,
+	const gchar *key, const gchar *value);
+
+void purple_http_request_header_set_printf(PurpleHttpRequest *request,
+	const gchar *key, const gchar *format, ...) G_GNUC_PRINTF(3, 4);
+
+/**
+ * Adds (without replacing, if exists) an HTTP request header.
+ *
+ * @param key   A header to be set.
+ * @param value A value to set.
+ *
+ * @see purple_http_request_header_set
+ */
+void purple_http_request_header_add(PurpleHttpRequest *request,
+	const gchar *key, const gchar *value);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name HTTP response API                                               */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Checks, if HTTP request was performed successfully.
+ *
+ * @param response The response.
+ * @return         TRUE, if request was performed successfully.
+ */
+gboolean purple_http_response_is_successfull(PurpleHttpResponse *response);
+
+/**
+ * Gets HTTP response code.
+ *
+ * @param response The response.
+ * @return         HTTP response code.
+ */
+int purple_http_response_get_code(PurpleHttpResponse *response);
+
+/**
+ * Gets error description.
+ *
+ * @param response The response.
+ * @return         Localized error description or NULL, if there was no error.
+ */
+const gchar * purple_http_response_get_error(PurpleHttpResponse *response);
+
+/**
+ * Gets HTTP response data length.
+ *
+ * @param response The response.
+ * @return         Data length;
+ */
+gsize purple_http_response_get_data_len(PurpleHttpResponse *response);
+
+/**
+ * Gets HTTP response data.
+ *
+ * Response data is not written, if writer callback was set for request.
+ *
+ * @param response The response.
+ * @return         The data.
+ */
+const gchar * purple_http_response_get_data(PurpleHttpResponse *response);
+
+/**
+ * Gets all headers got with response.
+ *
+ * @param response The response.
+ * @return         GList of PurpleKeyValuePair, which keys are header field
+ *                 names (gchar*) and values are its contents (gchar*).
+ */
+const GList * purple_http_response_get_all_headers(PurpleHttpResponse *response);
+
+/**
+ * Gets all headers with specified name got with response.
+ *
+ * @param response The response.
+ * @param name     The name of header field.
+ * @return         GList of header field records contents (gchar*).
+ */
+const GList * purple_http_response_get_headers_by_name(
+	PurpleHttpResponse *response, const gchar *name);
+
+/**
+ * Gets one header contents with specified name got with response.
+ *
+ * To get all headers with the same name, use
+ * purple_http_response_get_headers_by_name instead.
+ *
+ * @param response The response.
+ * @param name     The name of header field.
+ * @return         Header field contents or NULL, if there is no such one.
+ */
+const gchar * purple_http_response_get_header(PurpleHttpResponse *response,
+	const gchar *name);
+
+/*@}*/
+
+
+/**************************************************************************/
+/** @name HTTP Subsystem                                                  */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Initializes the http subsystem.
+ */
+void purple_http_init(void);
+
+/**
+ * Uninitializes the http subsystem.
+ */
+void purple_http_uninit(void);
+
+/*@}*/
+
+G_END_DECLS
+
+#endif /* _PURPLE_HTTP_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/obsolete.c	Mon Nov 05 18:15:24 2012 -0500
@@ -0,0 +1,773 @@
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301 USA
+ */
+
+#include "obsolete.h"
+
+#include "internal.h"
+#include "debug.h"
+#include "http.h"
+#include "ntlm.h"
+
+struct _PurpleUtilFetchUrlData
+{
+	PurpleUtilFetchUrlCallback callback;
+	void *user_data;
+
+	struct
+	{
+		char *user;
+		char *passwd;
+		char *address;
+		int port;
+		char *page;
+
+	} website;
+
+	char *url;
+	int num_times_redirected;
+	gboolean full;
+	char *user_agent;
+	gboolean http11;
+	char *request;
+	gsize request_written;
+	gboolean include_headers;
+
+	gboolean is_ssl;
+	PurpleSslConnection *ssl_connection;
+	PurpleProxyConnectData *connect_data;
+	int fd;
+	guint inpa;
+
+	gboolean got_headers;
+	gboolean has_explicit_data_len;
+	char *webdata;
+	gsize len;
+	unsigned long data_len;
+	gssize max_len;
+	gboolean chunked;
+	PurpleAccount *account;
+
+	PurpleHttpConnection *wrapped_request;
+	gboolean cancelled;
+};
+
+typedef struct
+{
+	PurpleUtilFetchUrlData *url_data;
+	PurpleUtilFetchUrlCallback cb;
+	gpointer user_data;
+} PurpleUtilLegacyWrapData;
+
+static void purple_util_fetch_url_cb(PurpleHttpConnection *http_conn,
+	PurpleHttpResponse *response, gpointer _wrap_data)
+{
+	PurpleUtilLegacyWrapData *wrap_data = _wrap_data;
+
+	if (wrap_data->cb && !wrap_data->url_data->cancelled)
+		wrap_data->cb(wrap_data->url_data, wrap_data->user_data,
+			purple_http_response_get_data(response),
+			purple_http_response_get_data_len(response),
+			purple_http_response_get_error(response));
+
+	g_free(wrap_data->url_data);
+	g_free(wrap_data);
+}
+
+PurpleUtilFetchUrlData * purple_util_fetch_url(const gchar *url, gboolean full,
+	const gchar *user_agent, gboolean http11, gssize max_len,
+	PurpleUtilFetchUrlCallback cb, gpointer data)
+{
+	PurpleHttpRequest *request;
+	PurpleUtilFetchUrlData *url_data;
+	PurpleUtilLegacyWrapData *wrap_data;
+
+	if (FALSE)
+		return purple_util_fetch_url_request(NULL, url, full,
+			user_agent, http11, NULL, FALSE, max_len, cb, data);
+
+	wrap_data = g_new0(PurpleUtilLegacyWrapData, 1);
+	url_data = g_new0(PurpleUtilFetchUrlData, 1);
+	request = purple_http_request_new(url);
+
+	wrap_data->url_data = url_data;
+	wrap_data->cb = cb;
+	wrap_data->user_data = data;
+
+	if (user_agent)
+		purple_http_request_header_set(request,
+			"User-Agent", user_agent);
+	purple_http_request_set_http11(request, http11);
+	purple_http_request_set_max_len(request, max_len);
+
+	url_data->wrapped_request = purple_http_request(NULL, request,
+		purple_util_fetch_url_cb, wrap_data);
+
+	purple_http_request_unref(request);
+
+	return url_data;
+}
+
+/**
+ * The arguments to this function are similar to printf.
+ */
+static void
+purple_util_fetch_url_error(PurpleUtilFetchUrlData *gfud, const char *format, ...)
+{
+	gchar *error_message;
+	va_list args;
+
+	va_start(args, format);
+	error_message = g_strdup_vprintf(format, args);
+	va_end(args);
+
+	gfud->callback(gfud, gfud->user_data, NULL, 0, error_message);
+	g_free(error_message);
+	purple_util_fetch_url_cancel(gfud);
+}
+
+static void url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message);
+static void ssl_url_fetch_connect_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond);
+static void ssl_url_fetch_error_cb(PurpleSslConnection *ssl_connection, PurpleSslErrorType error, gpointer data);
+
+static gboolean
+parse_redirect(const char *data, size_t data_len,
+			   PurpleUtilFetchUrlData *gfud)
+{
+	gchar *s;
+	gchar *new_url, *temp_url, *end;
+	gboolean full;
+	int len;
+
+	if ((s = g_strstr_len(data, data_len, "\nLocation: ")) == NULL)
+		/* We're not being redirected */
+		return FALSE;
+
+	s += strlen("Location: ");
+	end = strchr(s, '\r');
+
+	/* Just in case :) */
+	if (end == NULL)
+		end = strchr(s, '\n');
+
+	if (end == NULL)
+		return FALSE;
+
+	len = end - s;
+
+	new_url = g_malloc(len + 1);
+	strncpy(new_url, s, len);
+	new_url[len] = '\0';
+
+	full = gfud->full;
+
+	if (*new_url == '/' || g_strstr_len(new_url, len, "://") == NULL)
+	{
+		temp_url = new_url;
+
+		new_url = g_strdup_printf("%s:%d%s", gfud->website.address,
+								  gfud->website.port, temp_url);
+
+		g_free(temp_url);
+
+		full = FALSE;
+	}
+
+	purple_debug_info("util", "Redirecting to %s\n", new_url);
+
+	gfud->num_times_redirected++;
+	if (gfud->num_times_redirected >= 5)
+	{
+		purple_util_fetch_url_error(gfud,
+				_("Could not open %s: Redirected too many times"),
+				gfud->url);
+		return TRUE;
+	}
+
+	/*
+	 * Try again, with this new location.  This code is somewhat
+	 * ugly, but we need to reuse the gfud because whoever called
+	 * us is holding a reference to it.
+	 */
+	g_free(gfud->url);
+	gfud->url = new_url;
+	gfud->full = full;
+	g_free(gfud->request);
+	gfud->request = NULL;
+
+	if (gfud->is_ssl) {
+		gfud->is_ssl = FALSE;
+		purple_ssl_close(gfud->ssl_connection);
+		gfud->ssl_connection = NULL;
+	} else {
+		purple_input_remove(gfud->inpa);
+		gfud->inpa = 0;
+		close(gfud->fd);
+		gfud->fd = -1;
+	}
+	gfud->request_written = 0;
+	gfud->len = 0;
+	gfud->data_len = 0;
+
+	g_free(gfud->website.user);
+	g_free(gfud->website.passwd);
+	g_free(gfud->website.address);
+	g_free(gfud->website.page);
+	purple_url_parse(new_url, &gfud->website.address, &gfud->website.port,
+				   &gfud->website.page, &gfud->website.user, &gfud->website.passwd);
+
+	if (purple_strcasestr(new_url, "https://") != NULL) {
+		gfud->is_ssl = TRUE;
+		gfud->ssl_connection = purple_ssl_connect(gfud->account,
+				gfud->website.address, gfud->website.port,
+				ssl_url_fetch_connect_cb, ssl_url_fetch_error_cb, gfud);
+	} else {
+		gfud->connect_data = purple_proxy_connect(NULL, gfud->account,
+				gfud->website.address, gfud->website.port,
+				url_fetch_connect_cb, gfud);
+	}
+
+	if (gfud->ssl_connection == NULL && gfud->connect_data == NULL)
+	{
+		purple_util_fetch_url_error(gfud, _("Unable to connect to %s"),
+				gfud->website.address);
+	}
+
+	return TRUE;
+}
+
+static const char *
+find_header_content(const char *data, size_t data_len, const char *header, size_t header_len)
+{
+	const char *p = NULL;
+
+	if (header_len <= 0)
+		header_len = strlen(header);
+
+	/* Note: data is _not_ nul-terminated.  */
+	if (data_len > header_len) {
+		if (header[0] == '\n')
+			p = (g_ascii_strncasecmp(data, header + 1, header_len - 1) == 0) ? data : NULL;
+		if (!p)
+			p = purple_strcasestr(data, header);
+		if (p)
+			p += header_len;
+	}
+
+	/* If we can find the header at all, try to sscanf it.
+	 * Response headers should end with at least \r\n, so sscanf is safe,
+	 * if we make sure that there is indeed a \n in our header.
+	 */
+	if (p && g_strstr_len(p, data_len - (p - data), "\n")) {
+		return p;
+	}
+
+	return NULL;
+}
+
+static size_t
+parse_content_len(const char *data, size_t data_len)
+{
+	size_t content_len = 0;
+	const char *p = NULL;
+
+	p = find_header_content(data, data_len, "\nContent-Length: ", sizeof("\nContent-Length: ") - 1);
+	if (p) {
+		sscanf(p, "%" G_GSIZE_FORMAT, &content_len);
+		purple_debug_misc("util", "parsed %" G_GSIZE_FORMAT "\n", content_len);
+	}
+
+	return content_len;
+}
+
+static gboolean
+content_is_chunked(const char *data, size_t data_len)
+{
+	const char *p = find_header_content(data, data_len, "\nTransfer-Encoding: ", sizeof("\nTransfer-Encoding: ") - 1);
+	if (p && g_ascii_strncasecmp(p, "chunked", 7) == 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+/* Process in-place */
+static void
+process_chunked_data(char *data, gsize *len)
+{
+	gsize sz;
+	gsize newlen = 0;
+	char *p = data;
+	char *s = data;
+
+	while (*s) {
+		/* Read the size of this chunk */
+		if (sscanf(s, "%" G_GSIZE_MODIFIER "x", &sz) != 1)
+		{
+			purple_debug_error("util", "Error processing chunked data: "
+					"Expected data length, found: %s\n", s);
+			break;
+		}
+		if (sz == 0) {
+			/* We've reached the last chunk */
+			/*
+			 * TODO: The spec allows "footers" to follow the last chunk.
+			 *       If there is more data after this line then we should
+			 *       treat it like a header.
+			 */
+			break;
+		}
+
+		/* Advance to the start of the data */
+		s = strstr(s, "\r\n");
+		if (s == NULL)
+			break;
+		s += 2;
+
+		if (s + sz > data + *len) {
+			purple_debug_error("util", "Error processing chunked data: "
+					"Chunk size %" G_GSIZE_FORMAT " bytes was longer "
+					"than the data remaining in the buffer (%"
+					G_GSIZE_FORMAT " bytes)\n", sz, data + *len - s);
+		}
+
+		/* Move all data overtop of the chunk length that we read in earlier */
+		g_memmove(p, s, sz);
+		p += sz;
+		s += sz;
+		newlen += sz;
+		if (*s != '\r' && *(s + 1) != '\n') {
+			purple_debug_error("util", "Error processing chunked data: "
+					"Expected \\r\\n, found: %s\n", s);
+			break;
+		}
+		s += 2;
+	}
+
+	/* NULL terminate the data */
+	*p = 0;
+
+	*len = newlen;
+}
+
+static void
+url_fetch_recv_cb(gpointer url_data, gint source, PurpleInputCondition cond)
+{
+	PurpleUtilFetchUrlData *gfud = url_data;
+	int len;
+	char buf[4096];
+	char *data_cursor;
+	gboolean got_eof = FALSE;
+
+	/*
+	 * Read data in a loop until we can't read any more!  This is a
+	 * little confusing because we read using a different function
+	 * depending on whether the socket is ssl or cleartext.
+	 */
+	while ((gfud->is_ssl && ((len = purple_ssl_read(gfud->ssl_connection, buf, sizeof(buf))) > 0)) ||
+			(!gfud->is_ssl && (len = read(source, buf, sizeof(buf))) > 0))
+	{
+		if(gfud->max_len != -1 && (gfud->len + len) > gfud->max_len) {
+			purple_util_fetch_url_error(gfud, _("Error reading from %s: response too long (%d bytes limit)"),
+						    gfud->website.address, gfud->max_len);
+			return;
+		}
+
+		/* If we've filled up our buffer, make it bigger */
+		if((gfud->len + len) >= gfud->data_len) {
+			while((gfud->len + len) >= gfud->data_len)
+				gfud->data_len += sizeof(buf);
+
+			gfud->webdata = g_realloc(gfud->webdata, gfud->data_len);
+		}
+
+		data_cursor = gfud->webdata + gfud->len;
+
+		gfud->len += len;
+
+		memcpy(data_cursor, buf, len);
+
+		gfud->webdata[gfud->len] = '\0';
+
+		if(!gfud->got_headers) {
+			char *end_of_headers;
+
+			/* See if we've reached the end of the headers yet */
+			end_of_headers = strstr(gfud->webdata, "\r\n\r\n");
+			if (end_of_headers) {
+				char *new_data;
+				guint header_len = (end_of_headers + 4 - gfud->webdata);
+				size_t content_len;
+
+				purple_debug_misc("util", "Response headers: '%.*s'\n",
+					header_len, gfud->webdata);
+
+				/* See if we can find a redirect. */
+				if(parse_redirect(gfud->webdata, header_len, gfud))
+					return;
+
+				gfud->got_headers = TRUE;
+
+				/* No redirect. See if we can find a content length. */
+				content_len = parse_content_len(gfud->webdata, header_len);
+				gfud->chunked = content_is_chunked(gfud->webdata, header_len);
+
+				if (content_len == 0) {
+					/* We'll stick with an initial 8192 */
+					content_len = 8192;
+				} else {
+					gfud->has_explicit_data_len = TRUE;
+				}
+
+
+				/* If we're returning the headers too, we don't need to clean them out */
+				if (gfud->include_headers) {
+					gfud->data_len = content_len + header_len;
+					gfud->webdata = g_realloc(gfud->webdata, gfud->data_len);
+				} else {
+					size_t body_len = gfud->len - header_len;
+
+					content_len = MAX(content_len, body_len);
+
+					new_data = g_try_malloc(content_len);
+					if (new_data == NULL) {
+						purple_debug_error("util",
+								"Failed to allocate %" G_GSIZE_FORMAT " bytes: %s\n",
+								content_len, g_strerror(errno));
+						purple_util_fetch_url_error(gfud,
+								_("Unable to allocate enough memory to hold "
+								  "the contents from %s.  The web server may "
+								  "be trying something malicious."),
+								gfud->website.address);
+
+						return;
+					}
+
+					/* We may have read part of the body when reading the headers, don't lose it */
+					if (body_len > 0) {
+						memcpy(new_data, end_of_headers + 4, body_len);
+					}
+
+					/* Out with the old... */
+					g_free(gfud->webdata);
+
+					/* In with the new. */
+					gfud->len = body_len;
+					gfud->data_len = content_len;
+					gfud->webdata = new_data;
+				}
+			}
+		}
+
+		if(gfud->has_explicit_data_len && gfud->len >= gfud->data_len) {
+			got_eof = TRUE;
+			break;
+		}
+	}
+
+	if(len < 0) {
+		if(errno == EAGAIN) {
+			return;
+		} else {
+			purple_util_fetch_url_error(gfud, _("Error reading from %s: %s"),
+					gfud->website.address, g_strerror(errno));
+			return;
+		}
+	}
+
+	if((len == 0) || got_eof) {
+		gfud->webdata = g_realloc(gfud->webdata, gfud->len + 1);
+		gfud->webdata[gfud->len] = '\0';
+
+		if (!gfud->include_headers && gfud->chunked) {
+			/* Process only if we don't want the headers. */
+			process_chunked_data(gfud->webdata, &gfud->len);
+		}
+
+		gfud->callback(gfud, gfud->user_data, gfud->webdata, gfud->len, NULL);
+		purple_util_fetch_url_cancel(gfud);
+	}
+}
+
+static void ssl_url_fetch_recv_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond)
+{
+	url_fetch_recv_cb(data, -1, cond);
+}
+
+/**
+ * This function is called when the socket is available to be written
+ * to.
+ *
+ * @param source The file descriptor that can be written to.  This can
+ *        be an http connection or it can be the SSL connection of an
+ *        https request.  So be careful what you use it for!  If it's
+ *        an https request then use purple_ssl_write() instead of
+ *        writing to it directly.
+ */
+static void
+url_fetch_send_cb(gpointer data, gint source, PurpleInputCondition cond)
+{
+	PurpleUtilFetchUrlData *gfud;
+	int len, total_len;
+
+	gfud = data;
+
+	if (gfud->request == NULL) {
+
+		PurpleProxyInfo *gpi = purple_proxy_get_setup(gfud->account);
+		GString *request_str = g_string_new(NULL);
+
+		g_string_append_printf(request_str, "GET %s%s HTTP/%s\r\n"
+						    "Connection: close\r\n",
+			(gfud->full ? "" : "/"),
+			(gfud->full ? (gfud->url ? gfud->url : "") : (gfud->website.page ? gfud->website.page : "")),
+			(gfud->http11 ? "1.1" : "1.0"));
+
+		if (gfud->user_agent)
+			g_string_append_printf(request_str, "User-Agent: %s\r\n", gfud->user_agent);
+
+		/* Host header is not forbidden in HTTP/1.0 requests, and HTTP/1.1
+		 * clients must know how to handle the "chunked" transfer encoding.
+		 * Purple doesn't know how to handle "chunked", so should always send
+		 * the Host header regardless, to get around some observed problems
+		 */
+		g_string_append_printf(request_str, "Accept: */*\r\n"
+						    "Host: %s\r\n",
+			(gfud->website.address ? gfud->website.address : ""));
+
+		if (purple_proxy_info_get_username(gpi) != NULL
+				&& (purple_proxy_info_get_type(gpi) == PURPLE_PROXY_USE_ENVVAR
+					|| purple_proxy_info_get_type(gpi) == PURPLE_PROXY_HTTP)) {
+			/* This chunk of code was copied from proxy.c http_start_connect_tunneling()
+			 * This is really a temporary hack - we need a more complete proxy handling solution,
+			 * so I didn't think it was worthwhile to refactor for reuse
+			 */
+			char *t1, *t2, *ntlm_type1;
+			char hostname[256];
+			int ret;
+
+			ret = gethostname(hostname, sizeof(hostname));
+			hostname[sizeof(hostname) - 1] = '\0';
+			if (ret < 0 || hostname[0] == '\0') {
+				purple_debug_warning("util", "proxy - gethostname() failed -- is your hostname set?");
+				strcpy(hostname, "localhost");
+			}
+
+			t1 = g_strdup_printf("%s:%s",
+				purple_proxy_info_get_username(gpi),
+				purple_proxy_info_get_password(gpi) ?
+					purple_proxy_info_get_password(gpi) : "");
+			t2 = purple_base64_encode((const guchar *)t1, strlen(t1));
+			g_free(t1);
+
+			ntlm_type1 = purple_ntlm_gen_type1(hostname, "");
+
+			g_string_append_printf(request_str,
+				"Proxy-Authorization: Basic %s\r\n"
+				"Proxy-Authorization: NTLM %s\r\n"
+				"Proxy-Connection: Keep-Alive\r\n",
+				t2, ntlm_type1);
+			g_free(ntlm_type1);
+			g_free(t2);
+		}
+
+		g_string_append(request_str, "\r\n");
+
+		gfud->request = g_string_free(request_str, FALSE);
+	}
+
+	if(purple_debug_is_unsafe())
+		purple_debug_misc("util", "Request: '%s'\n", gfud->request);
+	else
+		purple_debug_misc("util", "request constructed\n");
+
+	total_len = strlen(gfud->request);
+
+	if (gfud->is_ssl)
+		len = purple_ssl_write(gfud->ssl_connection, gfud->request + gfud->request_written,
+				total_len - gfud->request_written);
+	else
+		len = write(gfud->fd, gfud->request + gfud->request_written,
+				total_len - gfud->request_written);
+
+	if (len < 0 && errno == EAGAIN)
+		return;
+	else if (len < 0) {
+		purple_util_fetch_url_error(gfud, _("Error writing to %s: %s"),
+				gfud->website.address, g_strerror(errno));
+		return;
+	}
+	gfud->request_written += len;
+
+	if (gfud->request_written < total_len)
+		return;
+
+	/* We're done writing our request, now start reading the response */
+	if (gfud->is_ssl) {
+		purple_input_remove(gfud->inpa);
+		gfud->inpa = 0;
+		purple_ssl_input_add(gfud->ssl_connection, ssl_url_fetch_recv_cb, gfud);
+	} else {
+		purple_input_remove(gfud->inpa);
+		gfud->inpa = purple_input_add(gfud->fd, PURPLE_INPUT_READ, url_fetch_recv_cb,
+			gfud);
+	}
+}
+
+static void
+url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message)
+{
+	PurpleUtilFetchUrlData *gfud;
+
+	gfud = url_data;
+	gfud->connect_data = NULL;
+
+	if (source == -1)
+	{
+		purple_util_fetch_url_error(gfud, _("Unable to connect to %s: %s"),
+				(gfud->website.address ? gfud->website.address : ""), error_message);
+		return;
+	}
+
+	gfud->fd = source;
+
+	gfud->inpa = purple_input_add(source, PURPLE_INPUT_WRITE,
+								url_fetch_send_cb, gfud);
+	url_fetch_send_cb(gfud, source, PURPLE_INPUT_WRITE);
+}
+
+static void ssl_url_fetch_connect_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond)
+{
+	PurpleUtilFetchUrlData *gfud;
+
+	gfud = data;
+
+	gfud->inpa = purple_input_add(ssl_connection->fd, PURPLE_INPUT_WRITE,
+			url_fetch_send_cb, gfud);
+	url_fetch_send_cb(gfud, ssl_connection->fd, PURPLE_INPUT_WRITE);
+}
+
+static void ssl_url_fetch_error_cb(PurpleSslConnection *ssl_connection, PurpleSslErrorType error, gpointer data)
+{
+	PurpleUtilFetchUrlData *gfud;
+
+	gfud = data;
+	gfud->ssl_connection = NULL;
+
+	purple_util_fetch_url_error(gfud, _("Unable to connect to %s: %s"),
+			(gfud->website.address ? gfud->website.address : ""),
+	purple_ssl_strerror(error));
+}
+
+PurpleUtilFetchUrlData *
+purple_util_fetch_url_request(PurpleAccount *account,
+		const char *url, gboolean full,	const char *user_agent, gboolean http11,
+		const char *request, gboolean include_headers, gssize max_len,
+		PurpleUtilFetchUrlCallback callback, void *user_data)
+{
+	PurpleUtilFetchUrlData *gfud;
+
+	g_return_val_if_fail(url      != NULL, NULL);
+	g_return_val_if_fail(callback != NULL, NULL);
+
+	if(purple_debug_is_unsafe())
+		purple_debug_info("util",
+				 "requested to fetch (%s), full=%d, user_agent=(%s), http11=%d\n",
+				 url, full, user_agent?user_agent:"(null)", http11);
+	else
+		purple_debug_info("util", "requesting to fetch a URL\n");
+
+	gfud = g_new0(PurpleUtilFetchUrlData, 1);
+
+	gfud->callback = callback;
+	gfud->user_data  = user_data;
+	gfud->url = g_strdup(url);
+	gfud->user_agent = g_strdup(user_agent);
+	gfud->http11 = http11;
+	gfud->full = full;
+	gfud->request = g_strdup(request);
+	gfud->include_headers = include_headers;
+	gfud->fd = -1;
+	gfud->max_len = max_len;
+	gfud->account = account;
+
+	purple_url_parse(url, &gfud->website.address, &gfud->website.port,
+				   &gfud->website.page, &gfud->website.user, &gfud->website.passwd);
+
+	if (purple_strcasestr(url, "https://") != NULL) {
+		if (!purple_ssl_is_supported()) {
+			purple_util_fetch_url_error(gfud,
+					_("Unable to connect to %s: %s"),
+					gfud->website.address,
+					_("Server requires TLS/SSL, but no TLS/SSL support was found."));
+			return NULL;
+		}
+
+		gfud->is_ssl = TRUE;
+		gfud->ssl_connection = purple_ssl_connect(account,
+				gfud->website.address, gfud->website.port,
+				ssl_url_fetch_connect_cb, ssl_url_fetch_error_cb, gfud);
+	} else {
+		gfud->connect_data = purple_proxy_connect(NULL, account,
+				gfud->website.address, gfud->website.port,
+				url_fetch_connect_cb, gfud);
+	}
+
+	if (gfud->ssl_connection == NULL && gfud->connect_data == NULL)
+	{
+		purple_util_fetch_url_error(gfud, _("Unable to connect to %s"),
+				gfud->website.address);
+		return NULL;
+	}
+
+	return gfud;
+}
+
+void
+purple_util_fetch_url_cancel(PurpleUtilFetchUrlData *gfud)
+{
+	if (gfud->wrapped_request != NULL) {
+		gfud->cancelled = TRUE;
+		purple_http_conn_cancel(gfud->wrapped_request);
+		return;
+	}
+
+	if (gfud->ssl_connection != NULL)
+		purple_ssl_close(gfud->ssl_connection);
+
+	if (gfud->connect_data != NULL)
+		purple_proxy_connect_cancel(gfud->connect_data);
+
+	if (gfud->inpa > 0)
+		purple_input_remove(gfud->inpa);
+
+	if (gfud->fd >= 0)
+		close(gfud->fd);
+
+	g_free(gfud->website.user);
+	g_free(gfud->website.passwd);
+	g_free(gfud->website.address);
+	g_free(gfud->website.page);
+	g_free(gfud->url);
+	g_free(gfud->user_agent);
+	g_free(gfud->request);
+	g_free(gfud->webdata);
+
+	g_free(gfud);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/obsolete.h	Mon Nov 05 18:15:24 2012 -0500
@@ -0,0 +1,110 @@
+/**
+ * @file obsolete.h Obsolete code, to be removed
+ * @ingroup core
+ */
+
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301 USA
+ */
+#ifndef _PURPLE_OBSOLETE_H_
+#define _PURPLE_OBSOLETE_H_
+
+#include <glib.h>
+#include "account.h"
+
+/**************************************************************************/
+/** @name URI/URL Functions                                               */
+/**************************************************************************/
+/*@{*/
+
+/**
+  * An opaque structure representing a URL request. Can be used to cancel
+  * the request.
+  */
+typedef struct _PurpleUtilFetchUrlData PurpleUtilFetchUrlData;
+
+/**
+ * This is the signature used for functions that act as the callback
+ * to purple_util_fetch_url() or purple_util_fetch_url_request().
+ *
+ * @param url_data      The same value that was returned when you called
+ *                      purple_fetch_url() or purple_fetch_url_request().
+ * @param user_data     The user data that your code passed into either
+ *                      purple_util_fetch_url() or purple_util_fetch_url_request().
+ * @param url_text      This will be NULL on error.  Otherwise this
+ *                      will contain the contents of the URL.
+ * @param len           0 on error, otherwise this is the length of buf.
+ * @param error_message If something went wrong then this will contain
+ *                      a descriptive error message, and buf will be
+ *                      NULL and len will be 0.
+ */
+typedef void (*PurpleUtilFetchUrlCallback)(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message);
+
+/**
+ * Fetches the data from a URL, and passes it to a callback function.
+ *
+ * @param url        The URL.
+ * @param full       TRUE if this is the full URL, or FALSE if it's a
+ *                   partial URL.
+ * @param user_agent The user agent field to use, or NULL.
+ * @param http11     TRUE if HTTP/1.1 should be used to download the file.
+ * @param max_len    The maximum number of bytes to retrieve (-1 for unlimited)
+ * @param cb         The callback function.
+ * @param data       The user data to pass to the callback function.
+ */
+PurpleUtilFetchUrlData * purple_util_fetch_url(const gchar *url, gboolean full,
+	const gchar *user_agent, gboolean http11, gssize max_len,
+	PurpleUtilFetchUrlCallback cb, gpointer data);
+
+/**
+ * Fetches the data from a URL, and passes it to a callback function.
+ *
+ * @param account    The account for which the request is needed, or NULL.
+ * @param url        The URL.
+ * @param full       TRUE if this is the full URL, or FALSE if it's a
+ *                   partial URL.
+ * @param user_agent The user agent field to use, or NULL.
+ * @param http11     TRUE if HTTP/1.1 should be used to download the file.
+ * @param request    A HTTP request to send to the server instead of the
+ *                   standard GET
+ * @param include_headers
+ *                   If TRUE, include the HTTP headers in the response.
+ * @param max_len    The maximum number of bytes to retrieve (-1 for unlimited)
+ * @param callback   The callback function.
+ * @param data       The user data to pass to the callback function.
+ */
+PurpleUtilFetchUrlData *purple_util_fetch_url_request(
+		PurpleAccount *account, const gchar *url,
+		gboolean full, const gchar *user_agent, gboolean http11,
+		const gchar *request, gboolean include_headers, gssize max_len,
+		PurpleUtilFetchUrlCallback callback, gpointer data);
+
+/**
+ * Cancel a pending URL request started with either
+ * purple_util_fetch_url_request() or purple_util_fetch_url().
+ *
+ * @param url_data The data returned when you initiated the URL fetch.
+ */
+void purple_util_fetch_url_cancel(PurpleUtilFetchUrlData *url_data);
+
+
+/*@}*/
+
+#endif /* _PURPLE_OBSOLETE_H_ */
--- a/libpurple/plugins/perl/common/Util.xs	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/plugins/perl/common/Util.xs	Mon Nov 05 18:15:24 2012 -0500
@@ -1,31 +1,5 @@
 #include "module.h"
 
-static void
-purple_perl_util_url_cb(PurpleUtilFetchUrlData *url_data, void *user_data,
-                        const gchar *url_text, size_t size,
-                        const gchar *error_message)
-{
-	SV *sv = (SV *)user_data;
-	dSP;
-	ENTER;
-	SAVETMPS;
-	PUSHMARK(SP);
-
-	XPUSHs(sv_2mortal(newSVpvn(url_text, size)));
-	PUTBACK;
-
-	call_sv(sv, G_EVAL | G_SCALAR);
-	SPAGAIN;
-
-	/* XXX Make sure this destroys it correctly and that we don't want
-	 * something like sv_2mortal(sv) or something else here instead. */
-	SvREFCNT_dec(sv);
-
-	PUTBACK;
-	FREETMPS;
-	LEAVE;
-}
-
 static void markup_find_tag_foreach(GQuark key_id, char *data, HV *hv) {
 	const char *key = NULL;
 	key = g_quark_to_string(key_id);
@@ -460,31 +434,6 @@
 MODULE = Purple::Util  PACKAGE = Purple::Util  PREFIX = purple_util_
 PROTOTYPES: ENABLE
 
- #XXX: expand...
-void
-purple_util_fetch_url(plugin, url, full, user_agent, http11, max_len, cb)
-	Purple::Plugin plugin
-	const char *url
-	gboolean full
-	const char *user_agent
-	gboolean http11
-	gssize max_len
-	SV * cb
-PREINIT:
-	PurpleUtilFetchUrlData *data;
-PPCODE:
-	/* XXX: i don't like this... only plugins can use it... */
-	SV *sv = purple_perl_sv_from_fun(plugin, cb);
-
-	if (sv != NULL) {
-		data = purple_util_fetch_url(url, full, user_agent, http11, max_len,
-		                      purple_perl_util_url_cb, sv);
-		XPUSHs(sv_2mortal(purple_perl_bless_object(data, "Purple::Util::FetchUrlData")));
-	} else {
-		purple_debug_warning("perl", "Callback not a valid type, only strings and coderefs allowed in purple_util_fetch_url.\n");
-		XSRETURN_UNDEF;
-	}
-
 void
 purple_util_set_user_dir(dir)
 	const char *dir
--- a/libpurple/plugins/perl/common/module.h	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/plugins/perl/common/module.h	Mon Nov 05 18:15:24 2012 -0500
@@ -271,7 +271,6 @@
 
 /* util.h */
 typedef PurpleInfoFieldFormatCallback	Purple__Util__InfoFieldFormatCallback;
-typedef PurpleUtilFetchUrlData	Purple__Util__FetchUrlData;
 typedef PurpleMenuAction *		Purple__Menu__Action;
 
 /* value.h */
--- a/libpurple/protocols/gg/avatar.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/gg/avatar.c	Mon Nov 05 18:15:24 2012 -0500
@@ -31,6 +31,7 @@
 
 #include <debug.h>
 #include <glibcompat.h>
+#include <obsolete.h>
 
 #include "gg.h"
 #include "utils.h"
--- a/libpurple/protocols/gg/oauth/oauth-purple.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/gg/oauth/oauth-purple.c	Mon Nov 05 18:15:24 2012 -0500
@@ -34,6 +34,7 @@
 #include "../xml.h"
 
 #include <debug.h>
+#include <obsolete.h>
 
 #define GGP_OAUTH_RESPONSE_MAX 10240
 
--- a/libpurple/protocols/gg/pubdir-prpl.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/gg/pubdir-prpl.c	Mon Nov 05 18:15:24 2012 -0500
@@ -31,6 +31,7 @@
 
 #include <debug.h>
 #include <request.h>
+#include <obsolete.h>
 
 #include "oauth/oauth-purple.h"
 #include "xml.h"
--- a/libpurple/protocols/jabber/google/relay.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/jabber/google/relay.c	Mon Nov 05 18:15:24 2012 -0500
@@ -20,6 +20,7 @@
 
 #include "internal.h"
 #include "debug.h"
+#include "obsolete.h"
 
 #include "relay.h"
 
--- a/libpurple/protocols/jabber/jabber.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/jabber/jabber.c	Mon Nov 05 18:15:24 2012 -0500
@@ -44,6 +44,7 @@
 #include "util.h"
 #include "version.h"
 #include "xmlnode.h"
+#include "obsolete.h"
 
 #include "auth.h"
 #include "buddy.h"
--- a/libpurple/protocols/jabber/useravatar.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/jabber/useravatar.c	Mon Nov 05 18:15:24 2012 -0500
@@ -22,6 +22,7 @@
  */
 
 #include "internal.h"
+#include "obsolete.h"
 
 #include "useravatar.h"
 #include "pep.h"
--- a/libpurple/protocols/msn/msn.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/msn/msn.c	Mon Nov 05 18:15:24 2012 -0500
@@ -27,6 +27,7 @@
 
 #include "debug.h"
 #include "request.h"
+#include "obsolete.h"
 
 #include "accountopt.h"
 #include "contact.h"
--- a/libpurple/protocols/msn/session.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/msn/session.c	Mon Nov 05 18:15:24 2012 -0500
@@ -24,6 +24,7 @@
 
 #include "internal.h"
 #include "debug.h"
+#include "obsolete.h"
 
 #include "error.h"
 #include "msnutils.h"
--- a/libpurple/protocols/msn/slp.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/msn/slp.c	Mon Nov 05 18:15:24 2012 -0500
@@ -24,6 +24,7 @@
 
 #include "internal.h"
 #include "debug.h"
+#include "obsolete.h"
 
 #include "slp.h"
 #include "slpcall.h"
--- a/libpurple/protocols/mxit/formcmds.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/mxit/formcmds.c	Mon Nov 05 18:15:24 2012 -0500
@@ -28,6 +28,7 @@
 #include <glib.h>
 
 #include "purple.h"
+#include "obsolete.h"
 
 #include "protocol.h"
 #include "mxit.h"
--- a/libpurple/protocols/mxit/login.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/mxit/login.c	Mon Nov 05 18:15:24 2012 -0500
@@ -25,6 +25,7 @@
 
 #include    "internal.h"
 #include	"purple.h"
+#include	"obsolete.h"
 
 #include	"protocol.h"
 #include	"mxit.h"
--- a/libpurple/protocols/mxit/markup.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/mxit/markup.c	Mon Nov 05 18:15:24 2012 -0500
@@ -25,6 +25,7 @@
 
 #include    "internal.h"
 #include	"purple.h"
+#include	"obsolete.h"
 
 #include	"protocol.h"
 #include	"mxit.h"
--- a/libpurple/protocols/mxit/protocol.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/mxit/protocol.c	Mon Nov 05 18:15:24 2012 -0500
@@ -25,6 +25,7 @@
 
 #include    "internal.h"
 #include	"purple.h"
+#include	"obsolete.h"
 
 #include	"protocol.h"
 #include	"mxit.h"
--- a/libpurple/protocols/myspace/user.h	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/myspace/user.h	Mon Nov 05 18:15:24 2012 -0500
@@ -20,6 +20,8 @@
 #ifndef _MYSPACE_USER_H
 #define _MYSPACE_USER_H
 
+#include "obsolete.h"
+
 /* Hold ephemeral information about buddies, for proto_data of PurpleBuddy. */
 /* GHashTable? */
 typedef struct _MsimUser
--- a/libpurple/protocols/oscar/oscar.h	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/oscar/oscar.h	Mon Nov 05 18:15:24 2012 -0500
@@ -35,6 +35,7 @@
 #include "eventloop.h"
 #include "proxy.h"
 #include "sslconn.h"
+#include "obsolete.h"
 
 #include <stdio.h>
 #include <string.h>
--- a/libpurple/protocols/yahoo/libymsg.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/yahoo/libymsg.c	Mon Nov 05 18:15:24 2012 -0500
@@ -22,6 +22,7 @@
  */
 
 #include "internal.h"
+#include "obsolete.h"
 
 #include "account.h"
 #include "accountopt.h"
--- a/libpurple/protocols/yahoo/yahoo_aliases.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/yahoo/yahoo_aliases.c	Mon Nov 05 18:15:24 2012 -0500
@@ -23,6 +23,7 @@
 
 
 #include "internal.h"
+#include "obsolete.h"
 
 #include "account.h"
 #include "accountopt.h"
--- a/libpurple/protocols/yahoo/yahoo_picture.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/yahoo/yahoo_picture.c	Mon Nov 05 18:15:24 2012 -0500
@@ -22,6 +22,7 @@
  */
 
 #include "internal.h"
+#include "obsolete.h"
 
 #include "account.h"
 #include "accountopt.h"
--- a/libpurple/protocols/yahoo/yahoo_profile.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/protocols/yahoo/yahoo_profile.c	Mon Nov 05 18:15:24 2012 -0500
@@ -24,6 +24,7 @@
 #define PHOTO_SUPPORT 1
 
 #include "internal.h"
+#include "obsolete.h"
 #include "debug.h"
 #include "notify.h"
 #include "util.h"
--- a/libpurple/upnp.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/upnp.c	Mon Nov 05 18:15:24 2012 -0500
@@ -34,6 +34,7 @@
 #include "signals.h"
 #include "util.h"
 #include "xmlnode.h"
+#include "obsolete.h"
 
 /***************************************************************
 ** General Defines                                             *
--- a/libpurple/util.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/util.c	Mon Nov 05 18:15:24 2012 -0500
@@ -28,51 +28,10 @@
 #include "core.h"
 #include "debug.h"
 #include "notify.h"
-#include "ntlm.h"
 #include "prpl.h"
 #include "prefs.h"
 #include "util.h"
 
-struct _PurpleUtilFetchUrlData
-{
-	PurpleUtilFetchUrlCallback callback;
-	void *user_data;
-
-	struct
-	{
-		char *user;
-		char *passwd;
-		char *address;
-		int port;
-		char *page;
-
-	} website;
-
-	char *url;
-	int num_times_redirected;
-	gboolean full;
-	char *user_agent;
-	gboolean http11;
-	char *request;
-	gsize request_written;
-	gboolean include_headers;
-
-	gboolean is_ssl;
-	PurpleSslConnection *ssl_connection;
-	PurpleProxyConnectData *connect_data;
-	int fd;
-	guint inpa;
-
-	gboolean got_headers;
-	gboolean has_explicit_data_len;
-	char *webdata;
-	gsize len;
-	unsigned long data_len;
-	gssize max_len;
-	gboolean chunked;
-	PurpleAccount *account;
-};
-
 struct _PurpleMenuAction
 {
 	char *label;
@@ -3742,9 +3701,9 @@
 		}
 
 		if (size_index == 0) {
-			return g_strdup_printf("%" G_GSIZE_FORMAT " %s", size, size_str[size_index]);
+			return g_strdup_printf("%" G_GOFFSET_FORMAT " %s", size, _(size_str[size_index]));
 		} else {
-			return g_strdup_printf("%.2f %s", size_mag, size_str[size_index]);
+			return g_strdup_printf("%.2f %s", size_mag, _(size_str[size_index]));
 		}
 	}
 }
@@ -4006,646 +3965,6 @@
 #undef PASSWD_CTRL
 }
 
-/**
- * The arguments to this function are similar to printf.
- */
-static void
-purple_util_fetch_url_error(PurpleUtilFetchUrlData *gfud, const char *format, ...)
-{
-	gchar *error_message;
-	va_list args;
-
-	va_start(args, format);
-	error_message = g_strdup_vprintf(format, args);
-	va_end(args);
-
-	gfud->callback(gfud, gfud->user_data, NULL, 0, error_message);
-	g_free(error_message);
-	purple_util_fetch_url_cancel(gfud);
-}
-
-static void url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message);
-static void ssl_url_fetch_connect_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond);
-static void ssl_url_fetch_error_cb(PurpleSslConnection *ssl_connection, PurpleSslErrorType error, gpointer data);
-
-static gboolean
-parse_redirect(const char *data, size_t data_len,
-			   PurpleUtilFetchUrlData *gfud)
-{
-	gchar *s;
-	gchar *new_url, *temp_url, *end;
-	gboolean full;
-	int len;
-
-	if ((s = g_strstr_len(data, data_len, "\nLocation: ")) == NULL)
-		/* We're not being redirected */
-		return FALSE;
-
-	s += strlen("Location: ");
-	end = strchr(s, '\r');
-
-	/* Just in case :) */
-	if (end == NULL)
-		end = strchr(s, '\n');
-
-	if (end == NULL)
-		return FALSE;
-
-	len = end - s;
-
-	new_url = g_malloc(len + 1);
-	strncpy(new_url, s, len);
-	new_url[len] = '\0';
-
-	full = gfud->full;
-
-	if (*new_url == '/' || g_strstr_len(new_url, len, "://") == NULL)
-	{
-		temp_url = new_url;
-
-		new_url = g_strdup_printf("%s:%d%s", gfud->website.address,
-								  gfud->website.port, temp_url);
-
-		g_free(temp_url);
-
-		full = FALSE;
-	}
-
-	purple_debug_info("util", "Redirecting to %s\n", new_url);
-
-	gfud->num_times_redirected++;
-	if (gfud->num_times_redirected >= 5)
-	{
-		purple_util_fetch_url_error(gfud,
-				_("Could not open %s: Redirected too many times"),
-				gfud->url);
-		return TRUE;
-	}
-
-	/*
-	 * Try again, with this new location.  This code is somewhat
-	 * ugly, but we need to reuse the gfud because whoever called
-	 * us is holding a reference to it.
-	 */
-	g_free(gfud->url);
-	gfud->url = new_url;
-	gfud->full = full;
-	g_free(gfud->request);
-	gfud->request = NULL;
-
-	if (gfud->is_ssl) {
-		gfud->is_ssl = FALSE;
-		purple_ssl_close(gfud->ssl_connection);
-		gfud->ssl_connection = NULL;
-	} else {
-		purple_input_remove(gfud->inpa);
-		gfud->inpa = 0;
-		close(gfud->fd);
-		gfud->fd = -1;
-	}
-	gfud->request_written = 0;
-	gfud->len = 0;
-	gfud->data_len = 0;
-
-	g_free(gfud->website.user);
-	g_free(gfud->website.passwd);
-	g_free(gfud->website.address);
-	g_free(gfud->website.page);
-	purple_url_parse(new_url, &gfud->website.address, &gfud->website.port,
-				   &gfud->website.page, &gfud->website.user, &gfud->website.passwd);
-
-	if (purple_strcasestr(new_url, "https://") != NULL) {
-		gfud->is_ssl = TRUE;
-		gfud->ssl_connection = purple_ssl_connect(gfud->account,
-				gfud->website.address, gfud->website.port,
-				ssl_url_fetch_connect_cb, ssl_url_fetch_error_cb, gfud);
-	} else {
-		gfud->connect_data = purple_proxy_connect(NULL, gfud->account,
-				gfud->website.address, gfud->website.port,
-				url_fetch_connect_cb, gfud);
-	}
-
-	if (gfud->ssl_connection == NULL && gfud->connect_data == NULL)
-	{
-		purple_util_fetch_url_error(gfud, _("Unable to connect to %s"),
-				gfud->website.address);
-	}
-
-	return TRUE;
-}
-
-static const char *
-find_header_content(const char *data, size_t data_len, const char *header, size_t header_len)
-{
-	const char *p = NULL;
-
-	if (header_len <= 0)
-		header_len = strlen(header);
-
-	/* Note: data is _not_ nul-terminated.  */
-	if (data_len > header_len) {
-		if (header[0] == '\n')
-			p = (g_ascii_strncasecmp(data, header + 1, header_len - 1) == 0) ? data : NULL;
-		if (!p)
-			p = purple_strcasestr(data, header);
-		if (p)
-			p += header_len;
-	}
-
-	/* If we can find the header at all, try to sscanf it.
-	 * Response headers should end with at least \r\n, so sscanf is safe,
-	 * if we make sure that there is indeed a \n in our header.
-	 */
-	if (p && g_strstr_len(p, data_len - (p - data), "\n")) {
-		return p;
-	}
-
-	return NULL;
-}
-
-static size_t
-parse_content_len(const char *data, size_t data_len)
-{
-	size_t content_len = 0;
-	const char *p = NULL;
-
-	p = find_header_content(data, data_len, "\nContent-Length: ", sizeof("\nContent-Length: ") - 1);
-	if (p) {
-		sscanf(p, "%" G_GSIZE_FORMAT, &content_len);
-		purple_debug_misc("util", "parsed %" G_GSIZE_FORMAT "\n", content_len);
-	}
-
-	return content_len;
-}
-
-static gboolean
-content_is_chunked(const char *data, size_t data_len)
-{
-	const char *p = find_header_content(data, data_len, "\nTransfer-Encoding: ", sizeof("\nTransfer-Encoding: ") - 1);
-	if (p && g_ascii_strncasecmp(p, "chunked", 7) == 0)
-		return TRUE;
-
-	return FALSE;
-}
-
-/* Process in-place */
-static void
-process_chunked_data(char *data, gsize *len)
-{
-	gsize sz;
-	gsize newlen = 0;
-	char *p = data;
-	char *s = data;
-
-	while (*s) {
-		/* Read the size of this chunk */
-		if (sscanf(s, "%" G_GSIZE_MODIFIER "x", &sz) != 1)
-		{
-			purple_debug_error("util", "Error processing chunked data: "
-					"Expected data length, found: %s\n", s);
-			break;
-		}
-		if (sz == 0) {
-			/* We've reached the last chunk */
-			/*
-			 * TODO: The spec allows "footers" to follow the last chunk.
-			 *       If there is more data after this line then we should
-			 *       treat it like a header.
-			 */
-			break;
-		}
-
-		/* Advance to the start of the data */
-		s = strstr(s, "\r\n");
-		if (s == NULL)
-			break;
-		s += 2;
-
-		if (s + sz > data + *len) {
-			purple_debug_error("util", "Error processing chunked data: "
-					"Chunk size %" G_GSIZE_FORMAT " bytes was longer "
-					"than the data remaining in the buffer (%"
-					G_GSIZE_FORMAT " bytes)\n", sz, data + *len - s);
-		}
-
-		/* Move all data overtop of the chunk length that we read in earlier */
-		g_memmove(p, s, sz);
-		p += sz;
-		s += sz;
-		newlen += sz;
-		if (*s != '\r' && *(s + 1) != '\n') {
-			purple_debug_error("util", "Error processing chunked data: "
-					"Expected \\r\\n, found: %s\n", s);
-			break;
-		}
-		s += 2;
-	}
-
-	/* NULL terminate the data */
-	*p = 0;
-
-	*len = newlen;
-}
-
-static void
-url_fetch_recv_cb(gpointer url_data, gint source, PurpleInputCondition cond)
-{
-	PurpleUtilFetchUrlData *gfud = url_data;
-	int len;
-	char buf[4096];
-	char *data_cursor;
-	gboolean got_eof = FALSE;
-
-	/*
-	 * Read data in a loop until we can't read any more!  This is a
-	 * little confusing because we read using a different function
-	 * depending on whether the socket is ssl or cleartext.
-	 */
-	while ((gfud->is_ssl && ((len = purple_ssl_read(gfud->ssl_connection, buf, sizeof(buf))) > 0)) ||
-			(!gfud->is_ssl && (len = read(source, buf, sizeof(buf))) > 0))
-	{
-		if(gfud->max_len != -1 && (gfud->len + len) > gfud->max_len) {
-			purple_util_fetch_url_error(gfud, _("Error reading from %s: response too long (%d bytes limit)"),
-						    gfud->website.address, gfud->max_len);
-			return;
-		}
-
-		/* If we've filled up our buffer, make it bigger */
-		if((gfud->len + len) >= gfud->data_len) {
-			while((gfud->len + len) >= gfud->data_len)
-				gfud->data_len += sizeof(buf);
-
-			gfud->webdata = g_realloc(gfud->webdata, gfud->data_len);
-		}
-
-		data_cursor = gfud->webdata + gfud->len;
-
-		gfud->len += len;
-
-		memcpy(data_cursor, buf, len);
-
-		gfud->webdata[gfud->len] = '\0';
-
-		if(!gfud->got_headers) {
-			char *end_of_headers;
-
-			/* See if we've reached the end of the headers yet */
-			end_of_headers = strstr(gfud->webdata, "\r\n\r\n");
-			if (end_of_headers) {
-				char *new_data;
-				guint header_len = (end_of_headers + 4 - gfud->webdata);
-				size_t content_len;
-
-				purple_debug_misc("util", "Response headers: '%.*s'\n",
-					header_len, gfud->webdata);
-
-				/* See if we can find a redirect. */
-				if(parse_redirect(gfud->webdata, header_len, gfud))
-					return;
-
-				gfud->got_headers = TRUE;
-
-				/* No redirect. See if we can find a content length. */
-				content_len = parse_content_len(gfud->webdata, header_len);
-				gfud->chunked = content_is_chunked(gfud->webdata, header_len);
-
-				if (content_len == 0) {
-					/* We'll stick with an initial 8192 */
-					content_len = 8192;
-				} else {
-					gfud->has_explicit_data_len = TRUE;
-				}
-
-
-				/* If we're returning the headers too, we don't need to clean them out */
-				if (gfud->include_headers) {
-					gfud->data_len = content_len + header_len;
-					gfud->webdata = g_realloc(gfud->webdata, gfud->data_len);
-				} else {
-					size_t body_len = gfud->len - header_len;
-
-					content_len = MAX(content_len, body_len);
-
-					new_data = g_try_malloc(content_len);
-					if (new_data == NULL) {
-						purple_debug_error("util",
-								"Failed to allocate %" G_GSIZE_FORMAT " bytes: %s\n",
-								content_len, g_strerror(errno));
-						purple_util_fetch_url_error(gfud,
-								_("Unable to allocate enough memory to hold "
-								  "the contents from %s.  The web server may "
-								  "be trying something malicious."),
-								gfud->website.address);
-
-						return;
-					}
-
-					/* We may have read part of the body when reading the headers, don't lose it */
-					if (body_len > 0) {
-						memcpy(new_data, end_of_headers + 4, body_len);
-					}
-
-					/* Out with the old... */
-					g_free(gfud->webdata);
-
-					/* In with the new. */
-					gfud->len = body_len;
-					gfud->data_len = content_len;
-					gfud->webdata = new_data;
-				}
-			}
-		}
-
-		if(gfud->has_explicit_data_len && gfud->len >= gfud->data_len) {
-			got_eof = TRUE;
-			break;
-		}
-	}
-
-	if(len < 0) {
-		if(errno == EAGAIN) {
-			return;
-		} else {
-			purple_util_fetch_url_error(gfud, _("Error reading from %s: %s"),
-					gfud->website.address, g_strerror(errno));
-			return;
-		}
-	}
-
-	if((len == 0) || got_eof) {
-		gfud->webdata = g_realloc(gfud->webdata, gfud->len + 1);
-		gfud->webdata[gfud->len] = '\0';
-
-		if (!gfud->include_headers && gfud->chunked) {
-			/* Process only if we don't want the headers. */
-			process_chunked_data(gfud->webdata, &gfud->len);
-		}
-
-		gfud->callback(gfud, gfud->user_data, gfud->webdata, gfud->len, NULL);
-		purple_util_fetch_url_cancel(gfud);
-	}
-}
-
-static void ssl_url_fetch_recv_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond)
-{
-	url_fetch_recv_cb(data, -1, cond);
-}
-
-/**
- * This function is called when the socket is available to be written
- * to.
- *
- * @param source The file descriptor that can be written to.  This can
- *        be an http connection or it can be the SSL connection of an
- *        https request.  So be careful what you use it for!  If it's
- *        an https request then use purple_ssl_write() instead of
- *        writing to it directly.
- */
-static void
-url_fetch_send_cb(gpointer data, gint source, PurpleInputCondition cond)
-{
-	PurpleUtilFetchUrlData *gfud;
-	int len, total_len;
-
-	gfud = data;
-
-	if (gfud->request == NULL) {
-
-		PurpleProxyInfo *gpi = purple_proxy_get_setup(gfud->account);
-		GString *request_str = g_string_new(NULL);
-
-		g_string_append_printf(request_str, "GET %s%s HTTP/%s\r\n"
-						    "Connection: close\r\n",
-			(gfud->full ? "" : "/"),
-			(gfud->full ? (gfud->url ? gfud->url : "") : (gfud->website.page ? gfud->website.page : "")),
-			(gfud->http11 ? "1.1" : "1.0"));
-
-		if (gfud->user_agent)
-			g_string_append_printf(request_str, "User-Agent: %s\r\n", gfud->user_agent);
-
-		/* Host header is not forbidden in HTTP/1.0 requests, and HTTP/1.1
-		 * clients must know how to handle the "chunked" transfer encoding.
-		 * Purple doesn't know how to handle "chunked", so should always send
-		 * the Host header regardless, to get around some observed problems
-		 */
-		g_string_append_printf(request_str, "Accept: */*\r\n"
-						    "Host: %s\r\n",
-			(gfud->website.address ? gfud->website.address : ""));
-
-		if (purple_proxy_info_get_username(gpi) != NULL
-				&& (purple_proxy_info_get_type(gpi) == PURPLE_PROXY_USE_ENVVAR
-					|| purple_proxy_info_get_type(gpi) == PURPLE_PROXY_HTTP)) {
-			/* This chunk of code was copied from proxy.c http_start_connect_tunneling()
-			 * This is really a temporary hack - we need a more complete proxy handling solution,
-			 * so I didn't think it was worthwhile to refactor for reuse
-			 */
-			char *t1, *t2, *ntlm_type1;
-			char hostname[256];
-			int ret;
-
-			ret = gethostname(hostname, sizeof(hostname));
-			hostname[sizeof(hostname) - 1] = '\0';
-			if (ret < 0 || hostname[0] == '\0') {
-				purple_debug_warning("util", "proxy - gethostname() failed -- is your hostname set?");
-				strcpy(hostname, "localhost");
-			}
-
-			t1 = g_strdup_printf("%s:%s",
-				purple_proxy_info_get_username(gpi),
-				purple_proxy_info_get_password(gpi) ?
-					purple_proxy_info_get_password(gpi) : "");
-			t2 = purple_base64_encode((const guchar *)t1, strlen(t1));
-			g_free(t1);
-
-			ntlm_type1 = purple_ntlm_gen_type1(hostname, "");
-
-			g_string_append_printf(request_str,
-				"Proxy-Authorization: Basic %s\r\n"
-				"Proxy-Authorization: NTLM %s\r\n"
-				"Proxy-Connection: Keep-Alive\r\n",
-				t2, ntlm_type1);
-			g_free(ntlm_type1);
-			g_free(t2);
-		}
-
-		g_string_append(request_str, "\r\n");
-
-		gfud->request = g_string_free(request_str, FALSE);
-	}
-
-	if(purple_debug_is_unsafe())
-		purple_debug_misc("util", "Request: '%s'\n", gfud->request);
-	else
-		purple_debug_misc("util", "request constructed\n");
-
-	total_len = strlen(gfud->request);
-
-	if (gfud->is_ssl)
-		len = purple_ssl_write(gfud->ssl_connection, gfud->request + gfud->request_written,
-				total_len - gfud->request_written);
-	else
-		len = write(gfud->fd, gfud->request + gfud->request_written,
-				total_len - gfud->request_written);
-
-	if (len < 0 && errno == EAGAIN)
-		return;
-	else if (len < 0) {
-		purple_util_fetch_url_error(gfud, _("Error writing to %s: %s"),
-				gfud->website.address, g_strerror(errno));
-		return;
-	}
-	gfud->request_written += len;
-
-	if (gfud->request_written < total_len)
-		return;
-
-	/* We're done writing our request, now start reading the response */
-	if (gfud->is_ssl) {
-		purple_input_remove(gfud->inpa);
-		gfud->inpa = 0;
-		purple_ssl_input_add(gfud->ssl_connection, ssl_url_fetch_recv_cb, gfud);
-	} else {
-		purple_input_remove(gfud->inpa);
-		gfud->inpa = purple_input_add(gfud->fd, PURPLE_INPUT_READ, url_fetch_recv_cb,
-			gfud);
-	}
-}
-
-static void
-url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message)
-{
-	PurpleUtilFetchUrlData *gfud;
-
-	gfud = url_data;
-	gfud->connect_data = NULL;
-
-	if (source == -1)
-	{
-		purple_util_fetch_url_error(gfud, _("Unable to connect to %s: %s"),
-				(gfud->website.address ? gfud->website.address : ""), error_message);
-		return;
-	}
-
-	gfud->fd = source;
-
-	gfud->inpa = purple_input_add(source, PURPLE_INPUT_WRITE,
-								url_fetch_send_cb, gfud);
-	url_fetch_send_cb(gfud, source, PURPLE_INPUT_WRITE);
-}
-
-static void ssl_url_fetch_connect_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond)
-{
-	PurpleUtilFetchUrlData *gfud;
-
-	gfud = data;
-
-	gfud->inpa = purple_input_add(ssl_connection->fd, PURPLE_INPUT_WRITE,
-			url_fetch_send_cb, gfud);
-	url_fetch_send_cb(gfud, ssl_connection->fd, PURPLE_INPUT_WRITE);
-}
-
-static void ssl_url_fetch_error_cb(PurpleSslConnection *ssl_connection, PurpleSslErrorType error, gpointer data)
-{
-	PurpleUtilFetchUrlData *gfud;
-
-	gfud = data;
-	gfud->ssl_connection = NULL;
-
-	purple_util_fetch_url_error(gfud, _("Unable to connect to %s: %s"),
-			(gfud->website.address ? gfud->website.address : ""),
-	purple_ssl_strerror(error));
-}
-
-PurpleUtilFetchUrlData *
-purple_util_fetch_url_request(PurpleAccount *account,
-		const char *url, gboolean full,	const char *user_agent, gboolean http11,
-		const char *request, gboolean include_headers, gssize max_len,
-		PurpleUtilFetchUrlCallback callback, void *user_data)
-{
-	PurpleUtilFetchUrlData *gfud;
-
-	g_return_val_if_fail(url      != NULL, NULL);
-	g_return_val_if_fail(callback != NULL, NULL);
-
-	if(purple_debug_is_unsafe())
-		purple_debug_info("util",
-				 "requested to fetch (%s), full=%d, user_agent=(%s), http11=%d\n",
-				 url, full, user_agent?user_agent:"(null)", http11);
-	else
-		purple_debug_info("util", "requesting to fetch a URL\n");
-
-	gfud = g_new0(PurpleUtilFetchUrlData, 1);
-
-	gfud->callback = callback;
-	gfud->user_data  = user_data;
-	gfud->url = g_strdup(url);
-	gfud->user_agent = g_strdup(user_agent);
-	gfud->http11 = http11;
-	gfud->full = full;
-	gfud->request = g_strdup(request);
-	gfud->include_headers = include_headers;
-	gfud->fd = -1;
-	gfud->max_len = max_len;
-	gfud->account = account;
-
-	purple_url_parse(url, &gfud->website.address, &gfud->website.port,
-				   &gfud->website.page, &gfud->website.user, &gfud->website.passwd);
-
-	if (purple_strcasestr(url, "https://") != NULL) {
-		if (!purple_ssl_is_supported()) {
-			purple_util_fetch_url_error(gfud,
-					_("Unable to connect to %s: %s"),
-					gfud->website.address,
-					_("Server requires TLS/SSL, but no TLS/SSL support was found."));
-			return NULL;
-		}
-
-		gfud->is_ssl = TRUE;
-		gfud->ssl_connection = purple_ssl_connect(account,
-				gfud->website.address, gfud->website.port,
-				ssl_url_fetch_connect_cb, ssl_url_fetch_error_cb, gfud);
-	} else {
-		gfud->connect_data = purple_proxy_connect(NULL, account,
-				gfud->website.address, gfud->website.port,
-				url_fetch_connect_cb, gfud);
-	}
-
-	if (gfud->ssl_connection == NULL && gfud->connect_data == NULL)
-	{
-		purple_util_fetch_url_error(gfud, _("Unable to connect to %s"),
-				gfud->website.address);
-		return NULL;
-	}
-
-	return gfud;
-}
-
-void
-purple_util_fetch_url_cancel(PurpleUtilFetchUrlData *gfud)
-{
-	if (gfud->ssl_connection != NULL)
-		purple_ssl_close(gfud->ssl_connection);
-
-	if (gfud->connect_data != NULL)
-		purple_proxy_connect_cancel(gfud->connect_data);
-
-	if (gfud->inpa > 0)
-		purple_input_remove(gfud->inpa);
-
-	if (gfud->fd >= 0)
-		close(gfud->fd);
-
-	g_free(gfud->website.user);
-	g_free(gfud->website.passwd);
-	g_free(gfud->website.address);
-	g_free(gfud->website.page);
-	g_free(gfud->url);
-	g_free(gfud->user_agent);
-	g_free(gfud->request);
-	g_free(gfud->webdata);
-
-	g_free(gfud);
-}
 
 const char *
 purple_url_decode(const char *str)
--- a/libpurple/util.h	Sun Oct 07 00:11:52 2012 -0400
+++ b/libpurple/util.h	Mon Nov 05 18:15:24 2012 -0500
@@ -32,12 +32,6 @@
 #include <stdio.h>
 
 /**
-  * An opaque structure representing a URL request. Can be used to cancel
-  * the request.
-  */
-typedef struct _PurpleUtilFetchUrlData PurpleUtilFetchUrlData;
-
-/**
  * A generic structure that contains information about an "action."  One
  * place this is is used is by PRPLs to tell the core the list of available
  * right-click actions for a buddy list row.
@@ -1165,70 +1159,6 @@
 						char **ret_path, char **ret_user, char **ret_passwd);
 
 /**
- * This is the signature used for functions that act as the callback
- * to purple_util_fetch_url() or purple_util_fetch_url_request().
- *
- * @param url_data      The same value that was returned when you called
- *                      purple_fetch_url() or purple_fetch_url_request().
- * @param user_data     The user data that your code passed into either
- *                      purple_util_fetch_url() or purple_util_fetch_url_request().
- * @param url_text      This will be NULL on error.  Otherwise this
- *                      will contain the contents of the URL.
- * @param len           0 on error, otherwise this is the length of buf.
- * @param error_message If something went wrong then this will contain
- *                      a descriptive error message, and buf will be
- *                      NULL and len will be 0.
- */
-typedef void (*PurpleUtilFetchUrlCallback)(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message);
-
-/**
- * Fetches the data from a URL, and passes it to a callback function.
- *
- * @param url        The URL.
- * @param full       TRUE if this is the full URL, or FALSE if it's a
- *                   partial URL.
- * @param user_agent The user agent field to use, or NULL.
- * @param http11     TRUE if HTTP/1.1 should be used to download the file.
- * @param max_len    The maximum number of bytes to retrieve (-1 for unlimited)
- * @param cb         The callback function.
- * @param data       The user data to pass to the callback function.
- */
-#define purple_util_fetch_url(url, full, user_agent, http11, max_len, cb, data) \
-	purple_util_fetch_url_request(NULL, url, full, user_agent, http11, NULL, \
-		FALSE, max_len, cb, data);
-
-/**
- * Fetches the data from a URL, and passes it to a callback function.
- *
- * @param account    The account for which the request is needed, or NULL.
- * @param url        The URL.
- * @param full       TRUE if this is the full URL, or FALSE if it's a
- *                   partial URL.
- * @param user_agent The user agent field to use, or NULL.
- * @param http11     TRUE if HTTP/1.1 should be used to download the file.
- * @param request    A HTTP request to send to the server instead of the
- *                   standard GET
- * @param include_headers
- *                   If TRUE, include the HTTP headers in the response.
- * @param max_len    The maximum number of bytes to retrieve (-1 for unlimited)
- * @param callback   The callback function.
- * @param data       The user data to pass to the callback function.
- */
-PurpleUtilFetchUrlData *purple_util_fetch_url_request(
-		PurpleAccount *account, const gchar *url,
-		gboolean full, const gchar *user_agent, gboolean http11,
-		const gchar *request, gboolean include_headers, gssize max_len,
-		PurpleUtilFetchUrlCallback callback, gpointer data);
-
-/**
- * Cancel a pending URL request started with either
- * purple_util_fetch_url_request() or purple_util_fetch_url().
- *
- * @param url_data The data returned when you initiated the URL fetch.
- */
-void purple_util_fetch_url_cancel(PurpleUtilFetchUrlData *url_data);
-
-/**
  * Decodes a URL into a plain string.
  *
  * This will change hex codes and such to their ascii equivalents.
--- a/pidgin/gtkdialogs.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/pidgin/gtkdialogs.c	Mon Nov 05 18:15:24 2012 -0500
@@ -207,6 +207,7 @@
 	{NULL,                      NULL, "Erdal Ronahi", "erdal.ronahi@gmail.com"},
 	{NULL,                      NULL, "Rizoyê Xerzî", "rizoxerzi@hotmail.com"},
 	{N_("Lao"),                 "lo", "Anousak Souphavah", "anousak@gmail.com"},
+	{N_("Lithuanian"),          "lt", "Algimantas Margevičius", "margevicius.algimantas@gmail.com"},
 	{N_("Maithili"),            "mai", "Sangeeta Kumari", "sangeeta_0975@yahoo.com"},
 	{NULL,                      NULL, "Rajesh Ranjan", "rajeshkajha@yahoo.com"},
 	{N_("Meadow Mari"),         "mhr", "David Preece", "davidpreece1@gmail.com"},
@@ -246,7 +247,6 @@
 	{NULL,                      NULL, "Viveka Nathan K", "vivekanathan@users.sourceforge.net"},
 	{N_("Telugu"),              "te", "Krishnababu Krottapalli", "krottapalli@ymail.com"},
 	{N_("Thai"),                "th", "Isriya Paireepairit", "markpeak@gmail.com"},
-	{N_("Turkish"),             "tr", "Serdar Soytetir", "tulliana@gmail.com"},
 	{N_("Ukranian"),            "uk", "Oleksandr Kovalenko", "alx.kovalenko@gmail.com"},
 	{N_("Urdu"),                "ur", "RKVS Raman", "rkvsraman@gmail.com"},
 	{N_("Vietnamese"),          "vi", "Nguyễn Vũ Hưng", "vuhung16plus@gmail.com"},
@@ -324,7 +324,8 @@
 	{N_("Swedish"),             "sv", "Tore Lundqvist", NULL},
 	{NULL,                      NULL, "Christian Rose", NULL},
 	{N_("Telugu"),              "te", "Mr. Subbaramaih", "info.gist@cdac.in"},
-	{N_("Turkish"),             "tr", "Ahmet Alp Balkan", NULL},
+	{N_("Turkish"),             "tr", "Serdar Soytetir", "tulliana@gmail.com"},
+	{NULL,                      "tr", "Ahmet Alp Balkan", NULL},
 	{N_("Vietnamese"),          "vi", N_("T.M.Thanh and the Gnome-Vi Team"), "gnomevi-list@lists.sf.net"},
 	{N_("Simplified Chinese"),  "zh_CN", "Hashao", NULL},
 	{NULL,                      NULL, "Rocky S. Lee", NULL},
--- a/pidgin/gtkprefs.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/pidgin/gtkprefs.c	Mon Nov 05 18:15:24 2012 -0500
@@ -26,6 +26,7 @@
  */
 #include "internal.h"
 #include "pidgin.h"
+#include "obsolete.h"
 
 #include "debug.h"
 #include "nat-pmp.h"
--- a/pidgin/gtksmiley.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/pidgin/gtksmiley.c	Mon Nov 05 18:15:24 2012 -0500
@@ -27,6 +27,7 @@
 
 #include "internal.h"
 #include "pidgin.h"
+#include "obsolete.h"
 
 #include "debug.h"
 #include "notify.h"
--- a/pidgin/gtkstatusbox.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/pidgin/gtkstatusbox.c	Mon Nov 05 18:15:24 2012 -0500
@@ -43,6 +43,7 @@
 #include <gdk/gdkkeysyms.h>
 
 #include "internal.h"
+#include "obsolete.h"
 
 #include "account.h"
 #include "buddyicon.h"
--- a/pidgin/plugins/relnot.c	Sun Oct 07 00:11:52 2012 -0400
+++ b/pidgin/plugins/relnot.c	Mon Nov 05 18:15:24 2012 -0500
@@ -36,6 +36,7 @@
 #include "debug.h"
 #include "gtkblist.h"
 #include "gtkutils.h"
+#include "http.h"
 #include "notify.h"
 #include "pidginstock.h"
 #include "prefs.h"
@@ -60,42 +61,20 @@
 	purple_notify_uri(NULL, PURPLE_WEBSITE);
 }
 
-static void
-version_fetch_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
-		const gchar *response, size_t len, const gchar *error_message)
+static void version_fetch_cb(PurpleHttpConnection *hc,
+	PurpleHttpResponse *response, gpointer user_data)
 {
 	gchar *cur_ver;
-	const char *tmp, *changelog;
-	char response_code[4];
+	const char *changelog;
 	GtkWidget *release_dialog;
 
 	GString *message;
 	int i = 0;
 
-	if(error_message || !response || !len)
+	if(!purple_http_response_is_successfull(response))
 		return;
 
-	memset(response_code, '\0', sizeof(response_code));
-	/* Parse the status code - the response should be in the form of "HTTP/?.? 200 ..." */
-	if ((tmp = strstr(response, " ")) != NULL) {
-		tmp++;
-		/* Read the 3 digit status code */
-		if (len - (tmp - response) > 3) {
-			memcpy(response_code, tmp, 3);
-		}
-	}
-
-	if (strcmp(response_code, "200") != 0) {
-		purple_debug_error("relnot", "Didn't recieve a HTTP status code of 200.\n");
-		return;
-	}
-
-	/* Go to the start of the data */
-	if((changelog = strstr(response, "\r\n\r\n")) == NULL) {
-		purple_debug_error("relnot", "Unable to find start of HTTP response data.\n");
-		return;
-	}
-	changelog += 4;
+	changelog = purple_http_response_get_data(response);
 
 	while(changelog[i] && changelog[i] != '\n') i++;
 
@@ -131,7 +110,7 @@
 {
 	int last_check = purple_prefs_get_int("/plugins/gtk/relnot/last_check");
 	if(!last_check || time(NULL) - last_check > MIN_CHECK_INTERVAL) {
-		gchar *url, *request;
+		gchar *url;
 		const char *host = "pidgin.im";
 
 		url = g_strdup_printf("http://%s/version.php?version=%s&build=%s",
@@ -144,18 +123,8 @@
 #endif
 		);
 
-		request = g_strdup_printf(
-				"GET %s HTTP/1.0\r\n"
-				"Connection: close\r\n"
-				"Accept: */*\r\n"
-				"Host: %s\r\n\r\n",
-				url,
-				host);
+		purple_http_get(NULL, url, version_fetch_cb, NULL);
 
-		purple_util_fetch_url_request(NULL, url, TRUE, NULL, FALSE,
-			request, TRUE, -1, version_fetch_cb, NULL);
-
-		g_free(request);
 		g_free(url);
 
 		purple_prefs_set_int("/plugins/gtk/relnot/last_check", time(NULL));

mercurial