Mon, 05 Nov 2012 18:15:24 -0500
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));