* 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 * 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 "purple-socket.h" #define PURPLE_HTTP_URL_CREDENTIALS_CHARS "a-z0-9.,~_/*!&%?=+\\^-" #define PURPLE_HTTP_MAX_RECV_BUFFER_LEN 10240 #define PURPLE_HTTP_MAX_READ_BUFFER_LEN 10240 #define PURPLE_HTTP_GZ_BUFF_LEN 1024 #define PURPLE_HTTP_REQUEST_DEFAULT_MAX_REDIRECTS 20 #define PURPLE_HTTP_REQUEST_DEFAULT_TIMEOUT 30 #define PURPLE_HTTP_REQUEST_DEFAULT_MAX_LENGTH 1048576 #define PURPLE_HTTP_REQUEST_HARD_MAX_LENGTH G_MAXINT32-1 #define PURPLE_HTTP_PROGRESS_WATCHER_DEFAULT_INTERVAL 250000 typedef struct _PurpleHttpSocket PurpleHttpSocket; typedef struct _PurpleHttpHeaders PurpleHttpHeaders; typedef struct _PurpleHttpKeepaliveHost PurpleHttpKeepaliveHost; typedef struct _PurpleHttpKeepaliveRequest PurpleHttpKeepaliveRequest; typedef struct _PurpleHttpGzStream PurpleHttpGzStream; PurpleHttpKeepaliveHost *host; struct _PurpleHttpRequest PurpleHttpHeaders *headers; PurpleHttpCookieJar *cookie_jar; PurpleHttpKeepalivePool *keepalive_pool; PurpleHttpContentReader contents_reader; gpointer contents_reader_data; PurpleHttpContentWriter response_writer; gpointer response_writer_data; struct _PurpleHttpConnection PurpleHttpCallback callback; PurpleHttpRequest *request; PurpleHttpResponse *response; PurpleHttpKeepaliveRequest *socket_request; PurpleHttpConnectionSet *connection_set; PurpleHttpSocket *socket; guint request_header_written, request_contents_written; gboolean main_header_got, headers_got; GString *response_buffer; PurpleHttpGzStream *gz_stream; GString *contents_reader_buffer; gboolean contents_reader_requested; guint length_got, length_got_decompressed; gboolean is_chunked, in_chunk, chunks_done; int chunk_length, chunk_got; GList *link_global, *link_gc; PurpleHttpProgressWatcher watcher; gpointer watcher_user_data; guint watcher_interval_threshold; gint64 watcher_last_call; guint watcher_delayed_handle; struct _PurpleHttpResponse PurpleHttpHeaders *headers; struct _PurpleHttpHeaders struct _PurpleHttpCookieJar struct _PurpleHttpKeepaliveRequest PurpleSocketConnectCb cb; PurpleHttpKeepaliveHost *host; struct _PurpleHttpKeepaliveHost PurpleHttpKeepalivePool *pool; GSList *sockets; /* list of PurpleHttpSocket */ GSList *queue; /* list of PurpleHttpKeepaliveRequest */ guint process_queue_timeout; struct _PurpleHttpKeepalivePool /* key: purple_http_socket_hash, value: PurpleHttpKeepaliveHost */ struct _PurpleHttpConnectionSet struct _PurpleHttpGzStream static time_t purple_http_rfc1123_to_time(const gchar *str); static gboolean purple_http_request_is_method(PurpleHttpRequest *request, 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); purple_http_conn_retry(PurpleHttpConnection *http_conn); 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, static gchar * purple_http_cookie_jar_gen(PurpleHttpCookieJar *cookie_jar); gchar * purple_http_cookie_jar_dump(PurpleHttpCookieJar *cjar); static PurpleHttpKeepaliveRequest * purple_http_keepalive_pool_request(PurpleHttpKeepalivePool *pool, PurpleConnection *gc, const gchar *host, int port, gboolean is_ssl, PurpleSocketConnectCb cb, gpointer user_data); purple_http_keepalive_pool_request_cancel(PurpleHttpKeepaliveRequest *req); purple_http_keepalive_pool_release(PurpleHttpSocket *hs, gboolean invalidate); purple_http_connection_set_remove(PurpleHttpConnectionSet *set, PurpleHttpConnection *http_conn); static GRegex *purple_http_re_url, *purple_http_re_url_host, * 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 PurpleConnection. static GHashTable *purple_http_cancelling_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}; gchar *d_date, *d_month, *d_year, *d_time; 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); 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); while (months[month] != NULL) if (0 == g_ascii_strcasecmp(d_month, months[month])) iso_date = g_strdup_printf("%s-%02d-%sT%s+00:00", d_year, month, d_date, d_time); purple_debug_warning("http", "Invalid month: %s\n", d_month); t = purple_str_to_time(iso_date, TRUE, NULL, NULL, NULL); /*** GZip streams *************************************************************/ static PurpleHttpGzStream * purple_http_gz_new(gsize max_output, gboolean is_deflate) PurpleHttpGzStream *gzs = g_new0(PurpleHttpGzStream, 1); windowBits = MAX_WBITS + 32; if (inflateInit2(&gzs->zs, windowBits) != Z_OK) { purple_debug_error("http", "Cannot initialize zlib stream\n"); gzs->max_output = max_output; purple_http_gz_put(PurpleHttpGzStream *gzs, const gchar *buf, gsize len) const gchar *compressed_buff; g_return_val_if_fail(gzs != NULL, NULL); g_return_val_if_fail(buf != NULL, NULL); g_string_append_len(gzs->pending, buf, len); compressed_buff = gzs->pending->str; compressed_len = gzs->pending->len; zs->next_in = (z_const Bytef*)compressed_buff; zs->avail_in = compressed_len; ret = g_string_new(NULL); while (zs->avail_in > 0) { gchar decompressed_buff[PURPLE_HTTP_GZ_BUFF_LEN]; zs->next_out = (Bytef*)decompressed_buff; zs->avail_out = sizeof(decompressed_buff); decompressed_len = zs->avail_out = sizeof(decompressed_buff); gzres = inflate(zs, Z_FULL_FLUSH); decompressed_len -= zs->avail_out; if (gzres == Z_OK || gzres == Z_STREAM_END) { if (decompressed_len == 0) if (gzs->decompressed + decompressed_len >= purple_debug_warning("http", "Maximum amount of" " decompressed data is reached\n"); decompressed_len = gzs->max_output - gzs->decompressed += decompressed_len; g_string_append_len(ret, decompressed_buff, if (gzres == Z_STREAM_END) purple_debug_error("http", "Decompression failed (%d): %s\n", gzres, g_string_free(gzs->pending, TRUE); gzs->pending = g_string_new_len((gchar*)zs->next_in, purple_http_gz_free(PurpleHttpGzStream *gzs) g_string_free(gzs->pending, TRUE); /*** HTTP Sockets *************************************************************/ purple_http_socket_hash(const gchar *host, int port, gboolean is_ssl) return g_strdup_printf("%c:%s:%d", (is_ssl ? 'S' : 'R'), host, port); static PurpleHttpSocket * purple_http_socket_connect_new(PurpleConnection *gc, const gchar *host, int port, gboolean is_ssl, PurpleSocketConnectCb cb, gpointer user_data) PurpleHttpSocket *hs = g_new0(PurpleHttpSocket, 1); hs->ps = purple_socket_new(gc); purple_socket_set_data(hs->ps, "hs", hs); purple_socket_set_tls(hs->ps, is_ssl); purple_socket_set_host(hs->ps, host); purple_socket_set_port(hs->ps, port); if (!purple_socket_connect(hs->ps, cb, user_data)) { purple_socket_destroy(hs->ps); if (purple_debug_is_verbose()) purple_debug_misc("http", "new socket created: %p\n", hs); purple_http_socket_close_free(PurpleHttpSocket *hs) if (purple_debug_is_verbose()) purple_debug_misc("http", "destroying socket: %p\n", hs); purple_socket_destroy(hs->ps); /*** 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, 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, 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); static void purple_http_headers_free_kvp(PurpleKeyValuePair *kvp) static void purple_http_headers_free(PurpleHttpHeaders *hdrs) g_hash_table_destroy(hdrs->by_name); g_list_free_full(hdrs->list, (GDestroyNotify)purple_http_headers_free_kvp); static void purple_http_headers_add(PurpleHttpHeaders *hdrs, const gchar *key, GList *named_values, *new_values; 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); g_hash_table_insert(hdrs->by_name, key_low, new_values); static void purple_http_headers_remove(PurpleHttpHeaders *hdrs, g_return_if_fail(hdrs != NULL); g_return_if_fail(key != NULL); if (!g_hash_table_remove(hdrs->by_name, key)) /* Could be optimized to O(1). */ it = g_list_first(hdrs->list); PurpleKeyValuePair *kvp = it->data; if (g_ascii_strcasecmp(kvp->key, key) != 0) 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) g_return_val_if_fail(hdrs != NULL, NULL); static GList * purple_http_headers_get_all_by_name( PurpleHttpHeaders *hdrs, const gchar *key) 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); static const gchar * purple_http_headers_get(PurpleHttpHeaders *hdrs, const GList *values = purple_http_headers_get_all_by_name(hdrs, key); static gboolean purple_http_headers_get_int(PurpleHttpHeaders *hdrs, const gchar *key, int *dst) str = purple_http_headers_get(hdrs, key); if (1 != sscanf(str, "%d", &val)) static gboolean purple_http_headers_match(PurpleHttpHeaders *hdrs, const gchar *key, const gchar *value) str = purple_http_headers_get(hdrs, key); if (str == NULL || value == NULL) return (g_ascii_strcasecmp(str, value) == 0); static gchar * purple_http_headers_dump(PurpleHttpHeaders *hdrs) GString *s = g_string_new(""); hdr = purple_http_headers_get_all(hdrs); PurpleKeyValuePair *kvp = hdr->data; 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 gboolean _purple_http_recv_loopbody(PurpleHttpConnection *hc, gint fd); static void _purple_http_recv(gpointer _hc, gint fd, PurpleInputCondition cond); static void _purple_http_send(gpointer _hc, gint fd, PurpleInputCondition cond); /* closes current connection (if exists), estabilishes one and proceeds with 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, hc->response->error = g_strdup_vprintf(format, args); if (purple_debug_is_verbose()) purple_debug_warning("http", "error: %s\n", hc->response->error); purple_http_conn_cancel(hc); static void _purple_http_gen_headers(PurpleHttpConnection *hc) gchar *request_url, *tmp_url = NULL; gboolean proxy_http = FALSE; const gchar *proxy_username, *proxy_password; g_return_if_fail(hc != NULL); if (hc->request_header != NULL) 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) hc->request_header = h = g_string_new(""); hc->request_header_written = 0; hc->request_contents_written = 0; request_url = tmp_url = purple_http_url_print(url); g_string_append_printf(h, "%s %s HTTP/%s\r\n", req->method ? req->method : "GET", req->http11 ? "1.1" : "1.0"); 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: "); g_string_append(h, hc->is_keepalive ? "Keep-Alive\r\n" : "close\r\n"); if (!purple_http_headers_get(hdrs, "accept")) g_string_append(h, "Accept: */*\r\n"); if (!purple_http_headers_get(hdrs, "accept-encoding")) g_string_append(h, "Accept-Encoding: gzip, deflate\r\n"); if (!purple_http_headers_get(hdrs, "content-length") && ( req->contents_length > 0 || purple_http_request_is_method(req, "post"))) g_string_append_printf(h, "Content-Length: %u\r\n", g_string_append(h, "Proxy-Connection: close\r\n"); /* TEST: proxy+KeepAlive */ proxy_username = purple_proxy_info_get_username(proxy); if (proxy_http && proxy_username != NULL && proxy_username[0] != '\0') { gchar *proxy_auth, *ntlm_type1, *tmp; proxy_password = purple_proxy_info_get_password(proxy); if (proxy_password == NULL) tmp = g_strdup_printf("%s:%s", proxy_username, proxy_password); proxy_auth = purple_base64_encode((const guchar *)tmp, len); ntlm_type1 = purple_ntlm_gen_type1(purple_get_host_name(), ""); g_string_append_printf(h, "Proxy-Authorization: Basic %s\r\n", g_string_append_printf(h, "Proxy-Authorization: NTLM %s\r\n", g_string_append(h, "Proxy-Connection: close\r\n"); /* TEST: proxy+KeepAlive */ memset(proxy_auth, 0, strlen(proxy_auth)); hdr = purple_http_headers_get_all(hdrs); PurpleKeyValuePair *kvp = hdr->data; 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_string_append_printf(h, "\r\n"); if (purple_debug_is_unsafe() && purple_debug_is_verbose()) { purple_debug_misc("http", "Generated request headers:\n%s", static gboolean _purple_http_recv_headers(PurpleHttpConnection *hc, const gchar *buf, int len) purple_debug_error("http", "Headers already got\n"); _purple_http_error(hc, _("Error parsing HTTP")); 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")); while ((eol = strstr(hc->response_buffer->str, "\r\n")) gchar *hdrline = hc->response_buffer->str; int hdrline_len = eol - hdrline; hdrline[hdrline_len] = '\0'; if (hdrline[0] == '\0') { if (!hc->main_header_got) { if (purple_debug_is_verbose() && purple_debug_misc("http", "Got keep-" "alive terminator from previous" purple_debug_warning("http", "Got empty" " line at the beginning - this " "may be a HTTP server quirk\n"); } else /* hc->main_header_got */ { if (purple_debug_is_verbose()) { purple_debug_misc("http", "Got headers " } else if (!hc->main_header_got) { hc->main_header_got = TRUE; delim = strchr(hdrline, ' '); if (delim == NULL || 1 != sscanf(delim + 1, "%d", purple_debug_warning("http", "Invalid response code\n"); _purple_http_error(hc, _("Error parsing HTTP")); if (purple_debug_is_verbose()) purple_debug_misc("http", "Got main header with code %d\n", if (purple_debug_is_verbose() && purple_debug_is_unsafe()) purple_debug_misc("http", "Got header: %s\n", delim = strchr(hdrline, ':'); if (delim == NULL || delim == hdrline) { purple_debug_warning("http", "Bad header delimiter\n"); _purple_http_error(hc, _("Error parsing HTTP")); purple_http_headers_add(hc->response->headers, hdrline, delim); g_string_erase(hc->response_buffer, 0, hdrline_len + 2); static gboolean _purple_http_recv_body_data(PurpleHttpConnection *hc, const gchar *buf, int len) GString *decompressed = NULL; if (hc->length_expected >= 0 && len + hc->length_got > (guint)hc->length_expected) len = hc->length_expected - hc->length_got; if (hc->gz_stream != NULL) { decompressed = purple_http_gz_put(hc->gz_stream, buf, len); if (decompressed == NULL) { _("Error while decompressing data")); g_assert(hc->request->max_length <= PURPLE_HTTP_REQUEST_HARD_MAX_LENGTH); if (hc->length_got_decompressed + len > hc->request->max_length) { purple_debug_warning("http", "Maximum length exceeded, truncating\n"); len = hc->request->max_length - hc->length_got_decompressed; hc->length_expected = hc->length_got; hc->length_got_decompressed += len; if (decompressed != NULL) g_string_free(decompressed, TRUE); if (hc->request->response_writer != NULL) { succ = hc->request->response_writer(hc, hc->response, buf, hc->length_got_decompressed, len, hc->request->response_writer_data); if (decompressed != NULL) g_string_free(decompressed, TRUE); purple_debug_error("http", "Cannot write using callback\n"); _("Error handling retrieved data")); if (hc->response->contents == NULL) hc->response->contents = g_string_new(""); g_string_append_len(hc->response->contents, buf, len); if (decompressed != NULL) g_string_free(decompressed, TRUE); purple_http_conn_notify_progress_watcher(hc); static gboolean _purple_http_recv_body_chunked(PurpleHttpConnection *hc, const gchar *buf, int len) 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")); while (hc->response_buffer->len > 0) { 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; if (!_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); line = hc->response_buffer->str; eol = strstr(line, "\r\n"); g_string_erase(hc->response_buffer, 0, 2); line = hc->response_buffer->str; eol = strstr(line, "\r\n"); /* 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")); if (1 != sscanf(line, "%x", &hc->chunk_length)) { if (purple_debug_is_unsafe()) purple_debug_warning("http", "Chunk length not found in [%s]\n", purple_debug_warning("http", "Chunk length not found\n"); _purple_http_error(hc, _("Error parsing HTTP")); 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) { static gboolean _purple_http_recv_body(PurpleHttpConnection *hc, const gchar *buf, int len) return _purple_http_recv_body_chunked(hc, buf, len); return _purple_http_recv_body_data(hc, buf, len); static gboolean _purple_http_recv_loopbody(PurpleHttpConnection *hc, gint fd) len = purple_socket_read(hc->socket->ps, (guchar*)buf, sizeof(buf)); got_anything = (len > 0); if (len < 0 && errno == EAGAIN) _purple_http_error(hc, _("Error reading from %s: %s"), hc->url->host, g_strerror(errno)); if (hc->request->max_length == 0) { /* It's definitely YHttpServer quirk. */ purple_debug_warning("http", "Got EOF, but no data was " "expected (this may be a server quirk)\n"); hc->length_expected = hc->length_got; if (hc->length_expected >= 0 && hc->length_got < (guint)hc->length_expected) { purple_debug_warning("http", "No more data while reading" _purple_http_error(hc, _("Error parsing HTTP")); hc->length_expected = hc->length_got; else if (hc->length_got == 0 && hc->socket->use_count > 1) { purple_debug_info("http", "Keep-alive connection " "expired (when reading), retrying...\n"); purple_http_conn_retry(hc); const gchar *server = purple_http_headers_get( hc->response->headers, "Server"); g_ascii_strcasecmp(server, "YHttpServer") == 0) purple_debug_warning("http", "No more data " "while parsing headers (YHttpServer " hc->length_expected = hc->length_got = 0; hc->length_got_decompressed = 0; purple_debug_warning("http", "No more data " "while parsing headers\n"); _purple_http_error(hc, _("Error parsing HTTP")); if (!hc->headers_got && len > 0) { if (!_purple_http_recv_headers(hc, buf, len)) gboolean is_gzip, is_deflate; if (!purple_http_headers_get_int(hc->response->headers, "Content-Length", &hc->length_expected)) hc->length_expected = -1; hc->is_chunked = (purple_http_headers_match( "Transfer-Encoding", "chunked")); is_gzip = purple_http_headers_match( hc->response->headers, "Content-Encoding", is_deflate = purple_http_headers_match( hc->response->headers, "Content-Encoding", if (is_gzip || is_deflate) hc->gz_stream = purple_http_gz_new( hc->request->max_length + 1, 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 (!_purple_http_recv_body(hc, buf, len)) if (hc->is_chunked && hc->chunks_done && hc->length_expected < 0) hc->length_expected = hc->length_got; if (hc->length_expected >= 0 && hc->length_got >= (guint)hc->length_expected) if (hc->is_chunked && !hc->chunks_done) { if (purple_debug_is_verbose()) { purple_debug_misc("http", "I need the terminating empty chunk\n"); purple_debug_warning("http", "No headers got\n"); _purple_http_error(hc, _("Error parsing HTTP")); if (purple_debug_is_unsafe() && purple_debug_is_verbose()) { gchar *hdrs = purple_http_headers_dump( purple_debug_misc("http", "Got response headers: %s\n", 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); if (hc->response->code == 407) { _purple_http_error(hc, _("Invalid proxy credentials")); redirect = purple_http_headers_get(hc->response->headers, if (redirect && (hc->request->max_redirects == -1 || hc->request->max_redirects > hc->redirects_count)) { PurpleHttpURL *url = purple_http_url_parse(redirect); if (purple_debug_is_unsafe()) purple_debug_warning("http", "Invalid redirect to %s\n", purple_debug_warning("http", _purple_http_error(hc, _("Error parsing HTTP")); purple_http_url_relative(hc->url, url); purple_http_url_free(url); _purple_http_reconnect(hc); _purple_http_disconnect(hc, TRUE); purple_http_connection_terminate(hc); static void _purple_http_recv(gpointer _hc, gint fd, PurpleInputCondition cond) PurpleHttpConnection *hc = _hc; while (_purple_http_recv_loopbody(hc, fd)); static void _purple_http_send_got_data(PurpleHttpConnection *hc, gboolean success, gboolean eof, size_t stored) g_return_if_fail(hc != NULL); _purple_http_error(hc, _("Error requesting data to write")); hc->contents_reader_requested = FALSE; g_string_set_size(hc->contents_reader_buffer, stored); 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; gboolean writing_headers; /* Waiting for data. This could be written more efficiently, by removing * (and later, adding) hs->inpa. */ if (hc->contents_reader_requested) _purple_http_gen_headers(hc); (hc->request_header_written < hc->request_header->len); 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); write_from = hc->contents_reader_buffer->str; write_len = hc->contents_reader_buffer->len; write_from = hc->request->contents + hc->request_contents_written; write_len = hc->request->contents_length - hc->request_contents_written; purple_debug_warning("http", "Nothing to write\n"); written = purple_socket_write(hc->socket->ps, (const guchar*)write_from, write_len); if (written < 0 && errno == EAGAIN) if (hc->request_header_written == 0 && hc->socket->use_count > 1) purple_debug_info("http", "Keep-alive connection " "expired (when writing), retrying...\n"); purple_http_conn_retry(hc); _purple_http_error(hc, _("Error writing to %s: %s"), hc->url->host, g_strerror(errno)); hc->request_header_written += written; purple_http_conn_notify_progress_watcher(hc); if (hc->request_header_written < hc->request_header->len) if (hc->request->contents_length > 0) 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_length > 0 && hc->request_contents_written < (guint)hc->request->contents_length) /* request is completely written, let's read the response */ purple_socket_watch(hc->socket->ps, PURPLE_INPUT_READ, static void _purple_http_disconnect(PurpleHttpConnection *hc, g_return_if_fail(hc != NULL); g_string_free(hc->request_header, TRUE); hc->request_header = NULL; g_string_free(hc->response_buffer, TRUE); hc->response_buffer = NULL; purple_http_keepalive_pool_request_cancel(hc->socket_request); purple_http_keepalive_pool_release(hc->socket, !is_graceful); _purple_http_connected(PurpleSocket *ps, const gchar *error, gpointer _hc) PurpleHttpSocket *hs = NULL; PurpleHttpConnection *hc = _hc; hs = purple_socket_get_data(ps, "hs"); hc->socket_request = NULL; _purple_http_error(hc, _("Unable to connect to %s: %s"), purple_socket_watch(ps, PURPLE_INPUT_WRITE, _purple_http_send, hc); static gboolean _purple_http_reconnect(PurpleHttpConnection *hc) g_return_val_if_fail(hc != NULL, FALSE); g_return_val_if_fail(hc->url != NULL, FALSE); _purple_http_disconnect(hc, TRUE); if (purple_debug_is_verbose()) { if (purple_debug_is_unsafe()) { gchar *urlp = purple_http_url_print(hc->url); purple_debug_misc("http", "Connecting to %s...\n", urlp); purple_debug_misc("http", "Connecting to %s...\n", if (g_strcmp0(url->protocol, "") == 0 || g_ascii_strcasecmp(url->protocol, "http") == 0) { } else if (g_ascii_strcasecmp(url->protocol, "https") == 0) { _purple_http_error(hc, _("Unsupported protocol: %s"), if (is_ssl && !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.")); if (hc->request->keepalive_pool != NULL) { hc->socket_request = purple_http_keepalive_pool_request( hc->request->keepalive_pool, hc->gc, url->host, url->port, is_ssl, _purple_http_connected, hc); hc->socket = purple_http_socket_connect_new(hc->gc, url->host, url->port, is_ssl, _purple_http_connected, hc); if (hc->socket_request == NULL && hc->socket == NULL) { _purple_http_error(hc, _("Unable to connect to %s"), url->host); 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; if (hc->response->contents != NULL) g_string_free(hc->response->contents, TRUE); hc->response->contents = NULL; hc->length_got_decompressed = 0; hc->length_expected = -1; purple_http_conn_notify_progress_watcher(hc); /*** 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); PurpleHttpConnection * purple_http_get(PurpleConnection *gc, PurpleHttpCallback callback, gpointer user_data, const gchar *url) 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); PurpleHttpConnection * purple_http_get_printf(PurpleConnection *gc, PurpleHttpCallback callback, gpointer user_data, const gchar *format, ...) PurpleHttpConnection *ret; g_return_val_if_fail(format != NULL, NULL); value = g_strdup_vprintf(format, args); ret = purple_http_get(gc, callback, user_data, value); PurpleHttpConnection * purple_http_request(PurpleConnection *gc, PurpleHttpRequest *request, PurpleHttpCallback callback, PurpleHttpConnection *hc; g_return_val_if_fail(request != NULL, NULL); if (request->url == NULL) { purple_debug_error("http", "Cannot perform new request - " if (g_hash_table_lookup(purple_http_cancelling_gc, gc)) { purple_debug_warning("http", "Cannot perform another HTTP " "request while cancelling all related with this " hc = purple_http_connection_new(request, gc); hc->user_data = user_data; hc->url = purple_http_url_parse(request->url); if (purple_debug_is_unsafe()) purple_debug_misc("http", "Performing new request %p for %s.\n", purple_debug_misc("http", "Performing new request %p to %s.\n", hc, hc->url ? hc->url->host : NULL); if (!hc->url || hc->url->host == NULL || hc->url->host[0] == '\0') { purple_debug_error("http", "Invalid URL requested.\n"); purple_http_connection_terminate(hc); _purple_http_reconnect(hc); hc->timeout_handle = purple_timeout_add_seconds(request->timeout, purple_http_request_timeout, hc); /*** HTTP connection API ******************************************************/ static void purple_http_connection_free(PurpleHttpConnection *hc); static gboolean purple_http_conn_notify_progress_watcher_timeout(gpointer _hc); static PurpleHttpConnection * purple_http_connection_new( PurpleHttpRequest *request, PurpleConnection *gc) PurpleHttpConnection *hc = g_new0(PurpleHttpConnection, 1); g_assert(request != NULL); purple_http_request_ref(request); hc->response = purple_http_response_new(); hc->is_keepalive = (request->keepalive_pool != NULL); 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); 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); static void purple_http_connection_free(PurpleHttpConnection *hc) purple_timeout_remove(hc->timeout_handle); if (hc->watcher_delayed_handle) purple_timeout_remove(hc->watcher_delayed_handle); if (hc->connection_set != NULL) purple_http_connection_set_remove(hc->connection_set, hc); 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); purple_http_gz_free(hc->gz_stream); g_string_free(hc->request_header, TRUE); purple_http_hc_list = g_list_delete_link(purple_http_hc_list, g_hash_table_remove(purple_http_hc_by_ptr, hc); 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); g_hash_table_insert(purple_http_hc_by_gc, /* 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_successful(hc->response) ? "successfully" : "without success"); hc->callback(hc, hc->response, hc->user_data); purple_http_connection_free(hc); void purple_http_conn_cancel(PurpleHttpConnection *http_conn) if (http_conn->is_cancelling) http_conn->is_cancelling = TRUE; if (purple_debug_is_verbose()) { purple_debug_misc("http", "Cancelling connection %p...\n", http_conn->response->code = 0; _purple_http_disconnect(http_conn, FALSE); purple_http_connection_terminate(http_conn); purple_http_conn_retry(PurpleHttpConnection *http_conn) purple_debug_info("http", "Retrying connection %p...\n", http_conn); http_conn->response->code = 0; _purple_http_disconnect(http_conn, FALSE); _purple_http_reconnect(http_conn); void purple_http_conn_cancel_all(PurpleConnection *gc) if (purple_debug_is_verbose()) { purple_debug_misc("http", "Cancelling all running HTTP " gc_list = g_hash_table_lookup(purple_http_hc_by_gc, gc); g_hash_table_insert(purple_http_cancelling_gc, gc, GINT_TO_POINTER(TRUE)); PurpleHttpConnection *hc = gc_list->data; gc_list = g_list_next(gc_list); purple_http_conn_cancel(hc); g_hash_table_remove(purple_http_cancelling_gc, gc); if (NULL != g_hash_table_lookup(purple_http_hc_by_gc, gc)) purple_debug_fatal("http", "Couldn't cancel all connections " "related to gc=%p (it shouldn't happen)\n", gc); gboolean purple_http_conn_is_running(PurpleHttpConnection *http_conn) 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( PurpleConnection * purple_http_conn_get_purple_connection( PurpleHttpConnection *http_conn) g_return_val_if_fail(http_conn != NULL, NULL); void purple_http_conn_set_progress_watcher(PurpleHttpConnection *http_conn, PurpleHttpProgressWatcher watcher, gpointer user_data, g_return_if_fail(http_conn != NULL); if (interval_threshold < 0) { PURPLE_HTTP_PROGRESS_WATCHER_DEFAULT_INTERVAL; 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) g_return_if_fail(hc != NULL); reading_state = hc->is_reading; total = hc->length_expected; processed = hc->length_got; total = hc->request->contents_length; processed = hc->request_contents_written; if (total != -1 && total < processed) { purple_debug_warning("http", "Processed too much\n"); now = g_get_monotonic_time(); if (hc->watcher_last_call + hc->watcher_interval_threshold > now && processed != total) { if (hc->watcher_delayed_handle) hc->watcher_delayed_handle = purple_timeout_add_seconds( 1 + hc->watcher_interval_threshold / 1000000, purple_http_conn_notify_progress_watcher_timeout, hc); if (hc->watcher_delayed_handle) purple_timeout_remove(hc->watcher_delayed_handle); hc->watcher_delayed_handle = 0; hc->watcher_last_call = now; hc->watcher(hc, reading_state, processed, total, hc->watcher_user_data); static gboolean purple_http_conn_notify_progress_watcher_timeout(gpointer _hc) PurpleHttpConnection *hc = _hc; purple_http_conn_notify_progress_watcher(hc); /*** 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); void purple_http_cookie_free(PurpleHttpCookie *cookie) void purple_http_cookie_jar_free(PurpleHttpCookieJar *cookie_jar); PurpleHttpCookieJar * purple_http_cookie_jar_new(void) PurpleHttpCookieJar *cjar = g_new0(PurpleHttpCookieJar, 1); cjar->tab = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)purple_http_cookie_free); void purple_http_cookie_jar_free(PurpleHttpCookieJar *cookie_jar) g_hash_table_destroy(cookie_jar->tab); void purple_http_cookie_jar_ref(PurpleHttpCookieJar *cookie_jar) g_return_if_fail(cookie_jar != NULL); PurpleHttpCookieJar * purple_http_cookie_jar_unref( PurpleHttpCookieJar *cookie_jar) g_return_val_if_fail(cookie_jar->ref_count > 0, NULL); if (cookie_jar->ref_count > 0) purple_http_cookie_jar_free(cookie_jar); static void purple_http_cookie_jar_parse(PurpleHttpCookieJar *cookie_jar, values = g_list_first(values); const gchar *cookie = values->data; const gchar *eqsign, *semicolon; 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); purple_debug_warning("http", "Invalid cookie."); name = g_strndup(cookie, eqsign - cookie); value = g_strndup(eqsign, semicolon - eqsign); value = g_strdup(eqsign); 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)) { g_match_info_fetch(match_info, 1); expires = purple_http_rfc1123_to_time( g_match_info_free(match_info); g_regex_unref(re_expires); purple_http_cookie_jar_set_ext(cookie_jar, name, value, expires); static gchar * purple_http_cookie_jar_gen(PurpleHttpCookieJar *cookie_jar) PurpleHttpCookie *cookie; g_return_val_if_fail(cookie_jar != NULL, NULL); g_hash_table_iter_init(&it, cookie_jar->tab); while (g_hash_table_iter_next(&it, (gpointer*)&key, if (cookie->expires != -1 && cookie->expires <= now) g_string_append_printf(str, "%s=%s; ", key, cookie->value); 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) PurpleHttpCookie *cookie = purple_http_cookie_new(value); cookie->expires = expires; g_hash_table_insert(cookie_jar->tab, g_strdup(name), cookie); g_hash_table_remove(cookie_jar->tab, name); const gchar * purple_http_cookie_jar_get(PurpleHttpCookieJar *cookie_jar, 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); gchar * purple_http_cookie_jar_dump(PurpleHttpCookieJar *cjar) 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: %" G_GINT64_FORMAT ")\n", key, cookie->value, (gint64)cookie->expires); 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; /*** HTTP Keep-Alive pool API *************************************************/ purple_http_keepalive_host_process_queue(PurpleHttpKeepaliveHost *host); purple_http_keepalive_host_free(gpointer _host) PurpleHttpKeepaliveHost *host = _host; g_slist_free_full(host->queue, (GDestroyNotify)purple_http_keepalive_pool_request_cancel); g_slist_free_full(host->sockets, (GDestroyNotify)purple_http_socket_close_free); if (host->process_queue_timeout > 0) { purple_timeout_remove(host->process_queue_timeout); host->process_queue_timeout = 0; PurpleHttpKeepalivePool * purple_http_keepalive_pool_new(void) PurpleHttpKeepalivePool *pool = g_new0(PurpleHttpKeepalivePool, 1); pool->by_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, purple_http_keepalive_host_free); purple_http_keepalive_pool_free(PurpleHttpKeepalivePool *pool) g_return_if_fail(pool != NULL); pool->is_destroying = TRUE; g_hash_table_destroy(pool->by_hash); purple_http_keepalive_pool_ref(PurpleHttpKeepalivePool *pool) g_return_if_fail(pool != NULL); PurpleHttpKeepalivePool * purple_http_keepalive_pool_unref(PurpleHttpKeepalivePool *pool) g_return_val_if_fail(pool->ref_count > 0, NULL); purple_http_keepalive_pool_free(pool); static PurpleHttpKeepaliveRequest * purple_http_keepalive_pool_request(PurpleHttpKeepalivePool *pool, PurpleConnection *gc, const gchar *host, int port, gboolean is_ssl, PurpleSocketConnectCb cb, gpointer user_data) PurpleHttpKeepaliveRequest *req; PurpleHttpKeepaliveHost *kahost; g_return_val_if_fail(pool != NULL, NULL); g_return_val_if_fail(host != NULL, NULL); if (pool->is_destroying) { purple_debug_error("http", "pool is destroying\n"); hash = purple_http_socket_hash(host, port, is_ssl); kahost = g_hash_table_lookup(pool->by_hash, hash); kahost = g_new0(PurpleHttpKeepaliveHost, 1); kahost->host = g_strdup(host); g_hash_table_insert(pool->by_hash, g_strdup(hash), kahost); req = g_new0(PurpleHttpKeepaliveRequest, 1); req->user_data = user_data; kahost->queue = g_slist_append(kahost->queue, req); purple_http_keepalive_host_process_queue(kahost); _purple_http_keepalive_socket_connected(PurpleSocket *ps, const gchar *error, gpointer _req) PurpleHttpSocket *hs = NULL; PurpleHttpKeepaliveRequest *req = _req; hs = purple_socket_get_data(ps, "hs"); req->cb(ps, error, req->user_data); _purple_http_keepalive_host_process_queue_cb(gpointer _host) PurpleHttpKeepaliveRequest *req; PurpleHttpKeepaliveHost *host = _host; PurpleHttpSocket *hs = NULL; g_return_val_if_fail(host != NULL, FALSE); host->process_queue_timeout = 0; PurpleHttpSocket *hs_current = it->data; if (!hs_current->is_busy) { /* There are no free sockets and we cannot create another one. */ if (hs == NULL && sockets_count >= host->pool->limit_per_host && host->pool->limit_per_host > 0) host->queue = g_slist_remove(host->queue, req); if (purple_debug_is_verbose()) { purple_debug_misc("http", "locking a (previously used) " purple_http_keepalive_host_process_queue(host); req->cb(hs->ps, NULL, req->user_data); hs = purple_http_socket_connect_new(req->gc, req->host->host, req->host->port, req->host->is_ssl, _purple_http_keepalive_socket_connected, req); purple_debug_error("http", "failed creating new socket"); if (purple_debug_is_verbose()) purple_debug_misc("http", "locking a (new) socket: %p\n", hs); host->sockets = g_slist_append(host->sockets, hs); purple_http_keepalive_host_process_queue(PurpleHttpKeepaliveHost *host) g_return_if_fail(host != NULL); if (host->process_queue_timeout > 0) host->process_queue_timeout = purple_timeout_add(0, _purple_http_keepalive_host_process_queue_cb, host); purple_http_keepalive_pool_request_cancel(PurpleHttpKeepaliveRequest *req) req->host->queue = g_slist_remove(req->host->queue, req); if (G_LIKELY(req->host)) { req->host->sockets = g_slist_remove(req->host->sockets, purple_http_socket_close_free(req->hs); /* req should already be free'd here */ req->cb(NULL, _("Cancelled"), req->user_data); purple_http_keepalive_pool_release(PurpleHttpSocket *hs, gboolean invalidate) PurpleHttpKeepaliveHost *host; if (purple_debug_is_verbose()) purple_debug_misc("http", "releasing a socket: %p\n", hs); purple_socket_watch(hs->ps, 0, NULL, NULL); purple_http_socket_close_free(hs); host->sockets = g_slist_remove(host->sockets, hs); purple_http_socket_close_free(hs); purple_http_keepalive_host_process_queue(host); purple_http_keepalive_pool_set_limit_per_host(PurpleHttpKeepalivePool *pool, g_return_if_fail(pool != NULL); pool->limit_per_host = limit; purple_http_keepalive_pool_get_limit_per_host(PurpleHttpKeepalivePool *pool) g_return_val_if_fail(pool != NULL, 0); return pool->limit_per_host; /*** HTTP connection set API **************************************************/ PurpleHttpConnectionSet * purple_http_connection_set_new(void) PurpleHttpConnectionSet *set; set = g_new0(PurpleHttpConnectionSet, 1); set->connections = g_hash_table_new(g_direct_hash, g_direct_equal); purple_http_connection_set_destroy(PurpleHttpConnectionSet *set) set->is_destroying = TRUE; PurpleHttpConnection *http_conn; g_hash_table_iter_init(&iter, set->connections); if (!g_hash_table_iter_next(&iter, (gpointer*)&http_conn, NULL)) purple_http_conn_cancel(http_conn); g_hash_table_destroy(set->connections); purple_http_connection_set_add(PurpleHttpConnectionSet *set, PurpleHttpConnection *http_conn) if (http_conn->connection_set == set) if (http_conn->connection_set != NULL) { purple_http_connection_set_remove(http_conn->connection_set, g_hash_table_insert(set->connections, http_conn, (gpointer)TRUE); http_conn->connection_set = set; purple_http_connection_set_remove(PurpleHttpConnectionSet *set, PurpleHttpConnection *http_conn) g_hash_table_remove(set->connections, http_conn); if (http_conn->connection_set == set) http_conn->connection_set = NULL; /*** Request API **************************************************************/ static void purple_http_request_free(PurpleHttpRequest *request); PurpleHttpRequest * purple_http_request_new(const gchar *url) PurpleHttpRequest *request; request = g_new0(PurpleHttpRequest, 1); request->url = g_strdup(url); request->headers = purple_http_headers_new(); request->cookie_jar = purple_http_cookie_jar_new(); request->keepalive_pool = purple_http_keepalive_pool_new(); request->timeout = PURPLE_HTTP_REQUEST_DEFAULT_TIMEOUT; request->max_redirects = PURPLE_HTTP_REQUEST_DEFAULT_MAX_REDIRECTS; request->max_length = PURPLE_HTTP_REQUEST_DEFAULT_MAX_LENGTH; static void purple_http_request_free(PurpleHttpRequest *request) purple_http_headers_free(request->headers); purple_http_cookie_jar_unref(request->cookie_jar); purple_http_keepalive_pool_unref(request->keepalive_pool); void purple_http_request_ref(PurpleHttpRequest *request) g_return_if_fail(request != NULL); PurpleHttpRequest * purple_http_request_unref(PurpleHttpRequest *request) g_return_val_if_fail(request->ref_count > 0, NULL); if (request->ref_count > 0) purple_http_request_free(request); void purple_http_request_set_url(PurpleHttpRequest *request, const gchar *url) g_return_if_fail(request != NULL); g_return_if_fail(url != NULL); request->url = g_strdup(url); void purple_http_request_set_url_printf(PurpleHttpRequest *request, const gchar *format, ...) g_return_if_fail(request != NULL); g_return_if_fail(format != NULL); value = g_strdup_vprintf(format, args); purple_http_request_set_url(request, value); const gchar * purple_http_request_get_url(PurpleHttpRequest *request) g_return_val_if_fail(request != NULL, NULL); void purple_http_request_set_method(PurpleHttpRequest *request, const gchar *method) g_return_if_fail(request != NULL); request->method = g_strdup(method); const gchar * purple_http_request_get_method(PurpleHttpRequest *request) g_return_val_if_fail(request != NULL, NULL); static gboolean purple_http_request_is_method(PurpleHttpRequest *request, g_return_val_if_fail(request != NULL, FALSE); g_return_val_if_fail(method != NULL, FALSE); rmethod = purple_http_request_get_method(request); return (g_ascii_strcasecmp(method, "get") == 0); return (g_ascii_strcasecmp(method, rmethod) == 0); purple_http_request_set_keepalive_pool(PurpleHttpRequest *request, PurpleHttpKeepalivePool *pool) g_return_if_fail(request != NULL); purple_http_keepalive_pool_ref(pool); if (request->keepalive_pool != NULL) { purple_http_keepalive_pool_unref(request->keepalive_pool); request->keepalive_pool = NULL; request->keepalive_pool = pool; PurpleHttpKeepalivePool * purple_http_request_get_keepalive_pool(PurpleHttpRequest *request) g_return_val_if_fail(request != NULL, FALSE); return request->keepalive_pool; 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; 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_response_writer(PurpleHttpRequest *request, PurpleHttpContentWriter writer, gpointer user_data) g_return_if_fail(request != NULL); request->response_writer = writer; request->response_writer_data = user_data; void purple_http_request_set_timeout(PurpleHttpRequest *request, int timeout) g_return_if_fail(request != NULL); request->timeout = timeout; int purple_http_request_get_timeout(PurpleHttpRequest *request) g_return_val_if_fail(request != NULL, -1); void purple_http_request_set_max_redirects(PurpleHttpRequest *request, g_return_if_fail(request != NULL); 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); void purple_http_request_set_max_len(PurpleHttpRequest *request, int max_len) g_return_if_fail(request != NULL); if (max_len < 0 || max_len > PURPLE_HTTP_REQUEST_HARD_MAX_LENGTH) max_len = PURPLE_HTTP_REQUEST_HARD_MAX_LENGTH; 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); purple_http_headers_add(request->headers, key, value); void purple_http_request_header_set_printf(PurpleHttpRequest *request, const gchar *key, const gchar *format, ...) g_return_if_fail(request != NULL); g_return_if_fail(key != NULL); g_return_if_fail(format != NULL); value = g_strdup_vprintf(format, args); purple_http_request_header_set(request, key, 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); static void purple_http_response_free(PurpleHttpResponse *response) if (response->contents != NULL) g_string_free(response->contents, TRUE); purple_http_headers_free(response->headers); gboolean purple_http_response_is_successful(PurpleHttpResponse *response) g_return_val_if_fail(response != NULL, FALSE); /* TODO: HTTP/1.1 100 Continue */ int purple_http_response_get_code(PurpleHttpResponse *response) g_return_val_if_fail(response != NULL, 0); const gchar * purple_http_response_get_error(PurpleHttpResponse *response) g_return_val_if_fail(response != NULL, NULL); if (response->error != NULL) if (!purple_http_response_is_successful(response)) { static gchar errmsg[200]; if (response->code <= 0) { g_snprintf(errmsg, sizeof(errmsg), _("Unknown HTTP error")); g_snprintf(errmsg, sizeof(errmsg), _("Invalid HTTP response code (%d)"), gsize purple_http_response_get_data_len(PurpleHttpResponse *response) g_return_val_if_fail(response != NULL, 0); if (response->contents == NULL) return response->contents->len; const gchar * purple_http_response_get_data(PurpleHttpResponse *response, size_t *len) g_return_val_if_fail(response != NULL, ""); if (response->contents != NULL) { ret = response->contents->str; *len = response->contents->len; 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, 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 ************************************************************/ purple_http_url_parse(const char *raw_url) 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", 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') { } else if (url->protocol != NULL) { url->protocol = g_ascii_strdown(url->protocol, -1); if (host_full[0] == '\0') { if (url->path[0] == '\0') { if ((url->protocol == NULL) != (host_full == NULL)) purple_debug_warning("http", "Protocol or host not present " if (!g_regex_match(purple_http_re_url_host, host_full, 0, if (purple_debug_is_verbose() && purple_debug_is_unsafe()) { purple_debug_warning("http", "Invalid host provided for URL: %s\n", purple_http_url_free(url); url->username = 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->username[0] == '\0') { if (url->password[0] == '\0') { if (url->host[0] == '\0') { } else if (url->host != NULL) { url->host = g_ascii_strdown(url->host, -1); g_match_info_free(match_info); if (url->protocol == NULL) url->protocol = g_strdup("http"); if (url->port == 0 && 0 == strcmp(url->protocol, "http")) if (url->port == 0 && 0 == strcmp(url->protocol, "https")) url->path = g_strdup("/"); purple_debug_warning("http", "URL path doesn't start with slash\n"); purple_http_url_free(PurpleHttpURL *parsed_url) g_free(parsed_url->protocol); g_free(parsed_url->username); g_free(parsed_url->password); g_free(parsed_url->host); g_free(parsed_url->path); g_free(parsed_url->fragment); 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->username); base_url->username = g_strdup(relative_url->username); g_free(base_url->password); base_url->password = g_strdup(relative_url->password); base_url->host = g_strdup(relative_url->host); base_url->port = relative_url->port; if (relative_url->path) { if (relative_url->path[0] == '/' || base_url->path == NULL) { base_url->path = g_strdup(relative_url->path); gchar *last_slash = strrchr(base_url->path, '/'); base_url->path[0] = '\0'; base_url->path = g_strconcat(base_url->path, relative_url->path, NULL); g_free(base_url->fragment); base_url->fragment = g_strdup(relative_url->fragment); 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, if (parsed_url->port == 443 && 0 == strcmp(parsed_url->protocol, if (parsed_url->username || parsed_url->password) { if (parsed_url->username) g_string_append(url, parsed_url->username); g_string_append_printf(url, ":%s", parsed_url->password); g_string_append(url, "@"); before_host_printed = TRUE; if (parsed_url->host || parsed_url->port) { g_string_append_printf(url, "{???}:%d", g_string_append(url, parsed_url->host); g_string_append_printf(url, ":%d", 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); purple_http_url_get_protocol(const PurpleHttpURL *parsed_url) g_return_val_if_fail(parsed_url != NULL, NULL); return parsed_url->protocol; purple_http_url_get_username(const PurpleHttpURL *parsed_url) g_return_val_if_fail(parsed_url != NULL, NULL); return parsed_url->username; purple_http_url_get_password(const PurpleHttpURL *parsed_url) g_return_val_if_fail(parsed_url != NULL, NULL); return parsed_url->password; purple_http_url_get_host(const PurpleHttpURL *parsed_url) g_return_val_if_fail(parsed_url != NULL, NULL); purple_http_url_get_port(const PurpleHttpURL *parsed_url) g_return_val_if_fail(parsed_url != NULL, 0); purple_http_url_get_path(const PurpleHttpURL *parsed_url) g_return_val_if_fail(parsed_url != NULL, NULL); purple_http_url_get_fragment(const PurpleHttpURL *parsed_url) g_return_val_if_fail(parsed_url != NULL, NULL); return parsed_url->fragment; /*** 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 */ "(?:#" "(.*)" ")?" /* 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]+:[0-9]+:[0-9]+) " /* time */ 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); purple_http_cancelling_gc = g_hash_table_new(g_direct_hash, g_direct_equal); 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, 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; g_hash_table_destroy(purple_http_cancelling_gc); purple_http_cancelling_gc = NULL;