Standardize all protocol header guard macros.
[pidgin-git.git] / libpurple / http.c
blob03dc2fc34cddb7bfd0f209b06dd3449da618908a
1 /* purple
3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
22 #include "http.h"
24 #include "internal.h"
25 #include "glibcompat.h"
28 #include "debug.h"
29 #include "proxy.h"
30 #include "purple-gio.h"
32 #define PURPLE_HTTP_URL_CREDENTIALS_CHARS "a-z0-9.,~_/*!&%?=+\\^-"
33 #define PURPLE_HTTP_MAX_RECV_BUFFER_LEN 102400
34 #define PURPLE_HTTP_MAX_READ_BUFFER_LEN 102400
35 #define PURPLE_HTTP_GZ_BUFF_LEN 1024
37 #define PURPLE_HTTP_REQUEST_DEFAULT_MAX_REDIRECTS 20
38 #define PURPLE_HTTP_REQUEST_DEFAULT_TIMEOUT 30
39 #define PURPLE_HTTP_REQUEST_DEFAULT_MAX_LENGTH 1048576
40 #define PURPLE_HTTP_REQUEST_HARD_MAX_LENGTH (G_MAXINT32 - 1)
42 #define PURPLE_HTTP_PROGRESS_WATCHER_DEFAULT_INTERVAL 250000
44 #define PURPLE_HTTP_GET_ACCOUNT(gc) (gc ? purple_connection_get_account(gc) : NULL)
46 typedef struct _PurpleHttpSocket PurpleHttpSocket;
48 typedef struct _PurpleHttpHeaders PurpleHttpHeaders;
50 typedef struct _PurpleHttpKeepaliveHost PurpleHttpKeepaliveHost;
52 typedef struct _PurpleHttpKeepaliveRequest PurpleHttpKeepaliveRequest;
54 typedef struct _PurpleHttpGzStream PurpleHttpGzStream;
56 typedef void (*PurpleHttpSocketConnectCb)(PurpleHttpSocket *hs,
57 const gchar *error, gpointer _hc);
59 struct _PurpleHttpSocket
61 GSocketConnection *conn;
62 GCancellable *cancellable;
63 guint input_source;
64 guint output_source;
66 gboolean is_busy;
67 guint use_count;
68 PurpleHttpKeepaliveHost *host;
71 struct _PurpleHttpRequest
73 int ref_count;
75 gchar *url;
76 gchar *method;
77 PurpleHttpHeaders *headers;
78 PurpleHttpCookieJar *cookie_jar;
79 PurpleHttpKeepalivePool *keepalive_pool;
81 gchar *contents;
82 int contents_length;
83 PurpleHttpContentReader contents_reader;
84 gpointer contents_reader_data;
85 PurpleHttpContentWriter response_writer;
86 gpointer response_writer_data;
88 int timeout;
89 int max_redirects;
90 gboolean http11;
91 guint max_length;
94 struct _PurpleHttpConnection
96 PurpleConnection *gc;
97 PurpleHttpCallback callback;
98 gpointer user_data;
99 gboolean is_reading;
100 gboolean is_keepalive;
101 gboolean is_cancelling;
103 PurpleHttpURL *url;
104 PurpleHttpRequest *request;
105 PurpleHttpResponse *response;
107 PurpleHttpKeepaliveRequest *socket_request;
108 PurpleHttpConnectionSet *connection_set;
109 PurpleHttpSocket *socket;
110 GString *request_header;
111 guint request_header_written, request_contents_written;
112 gboolean main_header_got, headers_got;
113 GString *response_buffer;
114 PurpleHttpGzStream *gz_stream;
116 GString *contents_reader_buffer;
117 gboolean contents_reader_requested;
119 int redirects_count;
121 int length_expected;
122 guint length_got, length_got_decompressed;
124 gboolean is_chunked, in_chunk, chunks_done;
125 int chunk_length, chunk_got;
127 GList *link_global, *link_gc;
129 guint timeout_handle;
131 PurpleHttpProgressWatcher watcher;
132 gpointer watcher_user_data;
133 guint watcher_interval_threshold;
134 gint64 watcher_last_call;
135 guint watcher_delayed_handle;
138 struct _PurpleHttpResponse
140 int code;
141 gchar *error;
143 GString *contents;
144 PurpleHttpHeaders *headers;
147 struct _PurpleHttpURL
149 gchar *protocol;
150 gchar *username;
151 gchar *password;
152 gchar *host;
153 int port;
154 gchar *path;
155 gchar *fragment;
158 struct _PurpleHttpHeaders
160 GList *list;
161 GHashTable *by_name;
164 typedef struct
166 time_t expires;
167 gchar *value;
168 } PurpleHttpCookie;
170 struct _PurpleHttpCookieJar
172 int ref_count;
174 GHashTable *tab;
177 struct _PurpleHttpKeepaliveRequest
179 PurpleConnection *gc;
180 PurpleHttpSocketConnectCb cb;
181 gpointer user_data;
183 PurpleHttpKeepaliveHost *host;
184 PurpleHttpSocket *hs;
187 struct _PurpleHttpKeepaliveHost
189 PurpleHttpKeepalivePool *pool;
191 gchar *host;
192 int port;
193 gboolean is_ssl;
195 GSList *sockets; /* list of PurpleHttpSocket */
197 GSList *queue; /* list of PurpleHttpKeepaliveRequest */
198 guint process_queue_timeout;
201 struct _PurpleHttpKeepalivePool
203 gboolean is_destroying;
205 int ref_count;
207 guint limit_per_host;
209 /* key: purple_http_socket_hash, value: PurpleHttpKeepaliveHost */
210 GHashTable *by_hash;
213 struct _PurpleHttpConnectionSet
215 gboolean is_destroying;
217 GHashTable *connections;
220 struct _PurpleHttpGzStream
222 gboolean failed;
223 GZlibDecompressor *decompressor;
224 gsize max_output;
225 gsize decompressed;
226 GString *pending;
229 struct _ntlm_type1_message {
230 guint8 protocol[8]; /* 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0' */
231 guint32 type; /* 0x00000001 */
232 guint32 flags; /* 0x0000b203 */
234 guint16 dom_len1; /* domain string length */
235 guint16 dom_len2; /* domain string length */
236 guint32 dom_off; /* domain string offset */
238 guint16 host_len1; /* host string length */
239 guint16 host_len2; /* host string length */
240 guint32 host_off; /* host string offset (always 0x00000020) */
243 static time_t purple_http_rfc1123_to_time(const gchar *str);
245 static gboolean purple_http_request_is_method(PurpleHttpRequest *request,
246 const gchar *method);
248 static PurpleHttpConnection * purple_http_connection_new(
249 PurpleHttpRequest *request, PurpleConnection *gc);
250 static void purple_http_connection_terminate(PurpleHttpConnection *hc);
251 static void purple_http_conn_notify_progress_watcher(PurpleHttpConnection *hc);
252 static void
253 purple_http_conn_retry(PurpleHttpConnection *http_conn);
255 static PurpleHttpResponse * purple_http_response_new(void);
256 static void purple_http_response_free(PurpleHttpResponse *response);
258 static void purple_http_cookie_jar_parse(PurpleHttpCookieJar *cookie_jar,
259 GList *values);
260 static gchar * purple_http_cookie_jar_gen(PurpleHttpCookieJar *cookie_jar);
261 gchar * purple_http_cookie_jar_dump(PurpleHttpCookieJar *cjar);
263 static PurpleHttpKeepaliveRequest *
264 purple_http_keepalive_pool_request(PurpleHttpKeepalivePool *pool,
265 PurpleConnection *gc, const gchar *host, int port, gboolean is_ssl,
266 PurpleHttpSocketConnectCb cb, gpointer user_data);
267 static void
268 purple_http_keepalive_pool_request_cancel(PurpleHttpKeepaliveRequest *req);
269 static void
270 purple_http_keepalive_pool_release(PurpleHttpSocket *hs, gboolean invalidate);
272 static void
273 purple_http_connection_set_remove(PurpleHttpConnectionSet *set,
274 PurpleHttpConnection *http_conn);
276 static GRegex *purple_http_re_url, *purple_http_re_url_host,
277 *purple_http_re_rfc1123;
280 * Values: pointers to running PurpleHttpConnection.
282 static GList *purple_http_hc_list;
285 * Keys: pointers to PurpleConnection.
286 * Values: GList of pointers to running PurpleHttpConnection.
288 static GHashTable *purple_http_hc_by_gc;
291 * Keys: pointers to PurpleConnection.
292 * Values: gboolean TRUE.
294 static GHashTable *purple_http_cancelling_gc;
297 * Keys: pointers to PurpleHttpConnection.
298 * Values: pointers to links in purple_http_hc_list.
300 static GHashTable *purple_http_hc_by_ptr;
302 /*** Helper functions *********************************************************/
304 static time_t purple_http_rfc1123_to_time(const gchar *str)
306 static const gchar *months[13] = {
307 "jan", "feb", "mar", "apr", "may", "jun",
308 "jul", "aug", "sep", "oct", "nov", "dec", NULL
310 GMatchInfo *match_info;
311 gchar *d_date, *d_month, *d_year, *d_time;
312 int month;
313 gchar *iso_date;
314 time_t t;
316 g_return_val_if_fail(str != NULL, 0);
318 g_regex_match(purple_http_re_rfc1123, str, 0, &match_info);
319 if (!g_match_info_matches(match_info)) {
320 g_match_info_free(match_info);
321 return 0;
324 d_date = g_match_info_fetch(match_info, 1);
325 d_month = g_match_info_fetch(match_info, 2);
326 d_year = g_match_info_fetch(match_info, 3);
327 d_time = g_match_info_fetch(match_info, 4);
329 g_match_info_free(match_info);
331 month = 0;
332 while (months[month] != NULL) {
333 if (0 == g_ascii_strcasecmp(d_month, months[month]))
334 break;
335 month++;
337 month++;
339 iso_date = g_strdup_printf("%s-%02d-%sT%s+00:00",
340 d_year, month, d_date, d_time);
342 g_free(d_date);
343 g_free(d_year);
344 g_free(d_time);
346 if (month > 12) {
347 purple_debug_warning("http", "Invalid month: %s\n", d_month);
348 g_free(d_month);
349 g_free(iso_date);
350 return 0;
353 g_free(d_month);
355 t = purple_str_to_time(iso_date, TRUE, NULL, NULL, NULL);
357 g_free(iso_date);
359 return t;
362 /*** GZip streams *************************************************************/
364 static PurpleHttpGzStream *
365 purple_http_gz_new(gsize max_output, gboolean is_deflate)
367 PurpleHttpGzStream *gzs = g_new0(PurpleHttpGzStream, 1);
368 GZlibCompressorFormat format;
370 if (is_deflate)
371 format = G_ZLIB_COMPRESSOR_FORMAT_RAW;
372 else /* is gzip */
373 format = G_ZLIB_COMPRESSOR_FORMAT_GZIP;
375 gzs->decompressor = g_zlib_decompressor_new(format);
376 gzs->max_output = max_output;
378 return gzs;
381 static GString *
382 purple_http_gz_put(PurpleHttpGzStream *gzs, const gchar *buf, gsize len)
384 const gchar *compressed_buff;
385 gsize compressed_len;
386 GString *ret;
388 g_return_val_if_fail(gzs != NULL, NULL);
389 g_return_val_if_fail(buf != NULL, NULL);
391 if (gzs->failed)
392 return NULL;
394 if (gzs->pending) {
395 g_string_append_len(gzs->pending, buf, len);
396 compressed_buff = gzs->pending->str;
397 compressed_len = gzs->pending->len;
398 } else {
399 compressed_buff = buf;
400 compressed_len = len;
403 ret = g_string_new(NULL);
404 while (compressed_len > 0) {
405 GConverterResult gzres;
406 gchar decompressed_buff[PURPLE_HTTP_GZ_BUFF_LEN];
407 gsize decompressed_len = 0;
408 gsize bytes_read = 0;
409 GError *error = NULL;
411 gzres = g_converter_convert(G_CONVERTER(gzs->decompressor),
412 compressed_buff, compressed_len,
413 decompressed_buff, sizeof(decompressed_buff),
414 G_CONVERTER_NO_FLAGS,
415 &bytes_read,
416 &decompressed_len,
417 &error);
419 compressed_buff += bytes_read;
420 compressed_len -= bytes_read;
422 if (gzres == G_CONVERTER_CONVERTED || gzres == G_CONVERTER_FINISHED) {
423 if (decompressed_len == 0)
424 break;
425 if (gzs->decompressed + decompressed_len >=
426 gzs->max_output)
428 purple_debug_warning("http", "Maximum amount of"
429 " decompressed data is reached\n");
430 decompressed_len = gzs->max_output -
431 gzs->decompressed;
432 gzres = G_CONVERTER_FINISHED;
434 gzs->decompressed += decompressed_len;
435 g_string_append_len(ret, decompressed_buff,
436 decompressed_len);
437 if (gzres == G_CONVERTER_FINISHED)
438 break;
439 } else {
440 purple_debug_error("http",
441 "Decompression failed (%d): %s\n", gzres,
442 error->message);
443 g_clear_error(&error);
444 gzs->failed = TRUE;
445 g_string_free(ret, TRUE);
446 return NULL;
450 if (gzs->pending) {
451 g_string_free(gzs->pending, TRUE);
452 gzs->pending = NULL;
455 if (compressed_len > 0) {
456 gzs->pending = g_string_new_len(compressed_buff,
457 compressed_len);
460 return ret;
463 static void
464 purple_http_gz_free(PurpleHttpGzStream *gzs)
466 if (gzs == NULL)
467 return;
468 g_object_unref(gzs->decompressor);
469 if (gzs->pending)
470 g_string_free(gzs->pending, TRUE);
471 g_free(gzs);
474 /*** NTLM *********************************************************************/
477 * purple_ntlm_gen_type1:
478 * @hostname: Your hostname
479 * @domain: The domain to authenticate to
481 * Generates the base64 encoded type 1 message needed for NTLM authentication
483 * Returns: base64 encoded string to send to the server. This should
484 * be g_free'd by the caller.
486 static gchar *
487 purple_http_ntlm_gen_type1(const gchar *hostname, const gchar *domain)
489 int hostnamelen,host_off;
490 int domainlen,dom_off;
491 unsigned char *msg;
492 struct _ntlm_type1_message *tmsg;
493 gchar *tmp;
495 hostnamelen = strlen(hostname);
496 domainlen = strlen(domain);
497 host_off = sizeof(struct _ntlm_type1_message);
498 dom_off = sizeof(struct _ntlm_type1_message) + hostnamelen;
499 msg = g_malloc0(sizeof(struct _ntlm_type1_message) + hostnamelen + domainlen);
500 tmsg = (struct _ntlm_type1_message*)(gpointer)msg;
501 tmsg->protocol[0] = 'N';
502 tmsg->protocol[1] = 'T';
503 tmsg->protocol[2] = 'L';
504 tmsg->protocol[3] = 'M';
505 tmsg->protocol[4] = 'S';
506 tmsg->protocol[5] = 'S';
507 tmsg->protocol[6] = 'P';
508 tmsg->protocol[7] = '\0';
509 tmsg->type = GUINT32_TO_LE(0x00000001);
510 tmsg->flags = GUINT32_TO_LE(0x0000b203);
511 tmsg->dom_len1 = tmsg->dom_len2 = GUINT16_TO_LE(domainlen);
512 tmsg->dom_off = GUINT32_TO_LE(dom_off);
513 tmsg->host_len1 = tmsg->host_len2 = GUINT16_TO_LE(hostnamelen);
514 tmsg->host_off = GUINT32_TO_LE(host_off);
515 memcpy(msg + host_off, hostname, hostnamelen);
516 memcpy(msg + dom_off, domain, domainlen);
518 tmp = g_base64_encode(msg, sizeof(struct _ntlm_type1_message) + hostnamelen + domainlen);
519 g_free(msg);
521 return tmp;
524 /*** HTTP Sockets *************************************************************/
526 static gchar *
527 purple_http_socket_hash(const gchar *host, int port, gboolean is_ssl)
529 return g_strdup_printf("%c:%s:%d", (is_ssl ? 'S' : 'R'), host, port);
532 static void
533 purple_http_socket_connect_new_cb(GObject *source, GAsyncResult *res,
534 gpointer user_data)
536 PurpleHttpSocket *hs = user_data;
537 GSocketConnection *conn;
538 PurpleHttpSocketConnectCb cb;
539 gpointer cb_data;
540 GError *error = NULL;
542 conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source),
543 res, &error);
545 cb = g_object_steal_data(source, "cb");
546 cb_data = g_object_steal_data(source, "cb_data");
548 if (conn == NULL) {
549 if (!g_error_matches(error,
550 G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
551 cb(hs, error->message, cb_data);
554 g_clear_error(&error);
555 return;
558 hs->conn = conn;
560 cb(hs, NULL, cb_data);
563 static PurpleHttpSocket *
564 purple_http_socket_connect_new(PurpleConnection *gc, const gchar *host,
565 int port, gboolean is_ssl,
566 PurpleHttpSocketConnectCb cb, gpointer user_data)
568 PurpleHttpSocket *hs;
569 GSocketClient *client;
570 GError *error = NULL;
572 client = purple_gio_socket_client_new(PURPLE_HTTP_GET_ACCOUNT(gc), &error);
574 if (client == NULL) {
575 purple_debug_error("http", "Error connecting to '%s:%d': %s",
576 host, port, error->message);
577 g_clear_error(&error);
578 return NULL;
581 hs = g_new0(PurpleHttpSocket, 1);
582 hs->cancellable = g_cancellable_new();
584 g_socket_client_set_tls(client, is_ssl);
585 g_object_set_data(G_OBJECT(client), "cb", cb);
586 g_object_set_data(G_OBJECT(client), "cb_data", user_data);
588 g_socket_client_connect_to_host_async(client,
589 host, port, hs->cancellable,
590 purple_http_socket_connect_new_cb, hs);
592 g_object_unref(client);
594 if (purple_debug_is_verbose())
595 purple_debug_misc("http", "new socket created: %p\n", hs);
597 return hs;
600 static void
601 purple_http_socket_close_free(PurpleHttpSocket *hs)
603 if (hs == NULL)
604 return;
606 if (purple_debug_is_verbose())
607 purple_debug_misc("http", "destroying socket: %p\n", hs);
609 if (hs->input_source > 0) {
610 g_source_remove(hs->input_source);
611 hs->input_source = 0;
614 if (hs->output_source > 0) {
615 g_source_remove(hs->output_source);
616 hs->output_source = 0;
619 if (hs->cancellable != NULL) {
620 g_cancellable_cancel(hs->cancellable);
621 g_clear_object(&hs->cancellable);
624 if (hs->conn != NULL) {
625 purple_gio_graceful_close(G_IO_STREAM(hs->conn), NULL, NULL);
626 g_clear_object(&hs->conn);
629 g_free(hs);
632 /*** Headers collection *******************************************************/
634 static PurpleHttpHeaders * purple_http_headers_new(void);
635 static void purple_http_headers_free(PurpleHttpHeaders *hdrs);
636 static void purple_http_headers_add(PurpleHttpHeaders *hdrs, const gchar *key,
637 const gchar *value);
638 static const GList * purple_http_headers_get_all(PurpleHttpHeaders *hdrs);
639 static GList * purple_http_headers_get_all_by_name(
640 PurpleHttpHeaders *hdrs, const gchar *key);
641 static const gchar * purple_http_headers_get(PurpleHttpHeaders *hdrs,
642 const gchar *key);
643 static gboolean purple_http_headers_get_int(PurpleHttpHeaders *hdrs,
644 const gchar *key, int *dst);
645 static gboolean purple_http_headers_match(PurpleHttpHeaders *hdrs,
646 const gchar *key, const gchar *value);
647 static gchar * purple_http_headers_dump(PurpleHttpHeaders *hdrs);
649 static PurpleHttpHeaders * purple_http_headers_new(void)
651 PurpleHttpHeaders *hdrs = g_new0(PurpleHttpHeaders, 1);
653 hdrs->by_name = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
654 (GDestroyNotify)g_list_free);
656 return hdrs;
659 static void purple_http_headers_free_kvp(PurpleKeyValuePair *kvp)
661 g_free(kvp->key);
662 g_free(kvp->value);
663 g_free(kvp);
666 static void purple_http_headers_free(PurpleHttpHeaders *hdrs)
668 if (hdrs == NULL)
669 return;
671 g_hash_table_destroy(hdrs->by_name);
672 g_list_free_full(hdrs->list,
673 (GDestroyNotify)purple_http_headers_free_kvp);
674 g_free(hdrs);
677 static void purple_http_headers_add(PurpleHttpHeaders *hdrs, const gchar *key,
678 const gchar *value)
680 PurpleKeyValuePair *kvp;
681 GList *named_values, *new_values;
682 gchar *key_low;
684 g_return_if_fail(hdrs != NULL);
685 g_return_if_fail(key != NULL);
686 g_return_if_fail(value != NULL);
688 kvp = g_new0(PurpleKeyValuePair, 1);
689 kvp->key = g_strdup(key);
690 kvp->value = g_strdup(value);
691 hdrs->list = g_list_append(hdrs->list, kvp);
693 key_low = g_ascii_strdown(key, -1);
694 named_values = g_hash_table_lookup(hdrs->by_name, key_low);
695 new_values = g_list_append(named_values, kvp->value);
696 if (named_values)
697 g_free(key_low);
698 else
699 g_hash_table_insert(hdrs->by_name, key_low, new_values);
702 static void purple_http_headers_remove(PurpleHttpHeaders *hdrs,
703 const gchar *key)
705 GList *it, *curr;
707 g_return_if_fail(hdrs != NULL);
708 g_return_if_fail(key != NULL);
710 if (!g_hash_table_remove(hdrs->by_name, key))
711 return;
713 /* Could be optimized to O(1). */
714 it = g_list_first(hdrs->list);
715 while (it) {
716 PurpleKeyValuePair *kvp = it->data;
717 curr = it;
718 it = g_list_next(it);
719 if (g_ascii_strcasecmp(kvp->key, key) != 0)
720 continue;
722 hdrs->list = g_list_delete_link(hdrs->list, curr);
723 purple_http_headers_free_kvp(kvp);
727 static const GList * purple_http_headers_get_all(PurpleHttpHeaders *hdrs)
729 g_return_val_if_fail(hdrs != NULL, NULL);
731 return hdrs->list;
734 /* return const */
735 static GList * purple_http_headers_get_all_by_name(
736 PurpleHttpHeaders *hdrs, const gchar *key)
738 GList *values;
739 gchar *key_low;
741 g_return_val_if_fail(hdrs != NULL, NULL);
742 g_return_val_if_fail(key != NULL, NULL);
744 key_low = g_ascii_strdown(key, -1);
745 values = g_hash_table_lookup(hdrs->by_name, key_low);
746 g_free(key_low);
748 return values;
751 static const gchar * purple_http_headers_get(PurpleHttpHeaders *hdrs,
752 const gchar *key)
754 const GList *values = purple_http_headers_get_all_by_name(hdrs, key);
756 if (!values)
757 return NULL;
759 return values->data;
762 static gboolean purple_http_headers_get_int(PurpleHttpHeaders *hdrs,
763 const gchar *key, int *dst)
765 int val;
766 const gchar *str;
768 str = purple_http_headers_get(hdrs, key);
769 if (!str)
770 return FALSE;
772 if (1 != sscanf(str, "%d", &val))
773 return FALSE;
775 *dst = val;
776 return TRUE;
779 static gboolean purple_http_headers_match(PurpleHttpHeaders *hdrs,
780 const gchar *key, const gchar *value)
782 const gchar *str;
784 str = purple_http_headers_get(hdrs, key);
785 if (str == NULL || value == NULL)
786 return str == value;
788 return (g_ascii_strcasecmp(str, value) == 0);
791 static gchar * purple_http_headers_dump(PurpleHttpHeaders *hdrs)
793 const GList *hdr;
795 GString *s = g_string_new("");
797 hdr = purple_http_headers_get_all(hdrs);
798 while (hdr) {
799 PurpleKeyValuePair *kvp = hdr->data;
800 hdr = g_list_next(hdr);
802 g_string_append_printf(s, "%s: %s%s", kvp->key,
803 (gchar*)kvp->value, hdr ? "\n" : "");
806 return g_string_free(s, FALSE);
809 /*** HTTP protocol backend ****************************************************/
811 static void _purple_http_disconnect(PurpleHttpConnection *hc,
812 gboolean is_graceful);
814 static void _purple_http_gen_headers(PurpleHttpConnection *hc);
815 static gboolean _purple_http_recv_loopbody(PurpleHttpConnection *hc);
816 static gboolean _purple_http_recv(GObject *source, gpointer _hc);
817 static gboolean _purple_http_send(GObject *source, gpointer _hc);
819 /* closes current connection (if exists), estabilishes one and proceeds with
820 * request */
821 static gboolean _purple_http_reconnect(PurpleHttpConnection *hc);
823 static void _purple_http_error(PurpleHttpConnection *hc, const char *format,
824 ...) G_GNUC_PRINTF(2, 3);
826 static void _purple_http_error(PurpleHttpConnection *hc, const char *format,
827 ...)
829 va_list args;
831 va_start(args, format);
832 hc->response->error = g_strdup_vprintf(format, args);
833 va_end(args);
835 if (purple_debug_is_verbose())
836 purple_debug_warning("http", "error: %s\n", hc->response->error);
838 purple_http_conn_cancel(hc);
841 static void memset_zero(gpointer pnt, gsize len)
843 volatile unsigned char *volatile pnt_ =
844 (volatile unsigned char *volatile) pnt;
845 size_t i = (size_t) 0U;
847 while (i < len) {
848 pnt_[i++] = 0U;
852 static void _purple_http_gen_headers(PurpleHttpConnection *hc)
854 GString *h;
855 PurpleHttpURL *url;
856 const GList *hdr;
857 PurpleHttpRequest *req;
858 PurpleHttpHeaders *hdrs;
859 gchar *request_url, *tmp_url = NULL;
861 PurpleProxyInfo *proxy;
862 gboolean proxy_http = FALSE;
863 const gchar *proxy_username, *proxy_password;
865 g_return_if_fail(hc != NULL);
867 if (hc->request_header != NULL)
868 return;
870 req = hc->request;
871 url = hc->url;
872 hdrs = req->headers;
873 proxy = purple_proxy_get_setup(PURPLE_HTTP_GET_ACCOUNT(hc->gc));
875 proxy_http = (purple_proxy_info_get_proxy_type(proxy) == PURPLE_PROXY_HTTP ||
876 purple_proxy_info_get_proxy_type(proxy) == PURPLE_PROXY_USE_ENVVAR);
877 /* this is HTTP proxy, but used with tunelling with CONNECT */
878 if (proxy_http && url->port != 80)
879 proxy_http = FALSE;
881 hc->request_header = h = g_string_new("");
882 hc->request_header_written = 0;
883 hc->request_contents_written = 0;
885 if (proxy_http)
886 request_url = tmp_url = purple_http_url_print(url);
887 else
888 request_url = url->path;
890 g_string_append_printf(h, "%s %s HTTP/%s\r\n",
891 req->method ? req->method : "GET",
892 request_url,
893 req->http11 ? "1.1" : "1.0");
895 g_free(tmp_url);
897 if (!purple_http_headers_get(hdrs, "host"))
898 g_string_append_printf(h, "Host: %s\r\n", url->host);
899 if (!purple_http_headers_get(hdrs, "connection")) {
900 g_string_append(h, "Connection: ");
901 g_string_append(h, hc->is_keepalive ?
902 "Keep-Alive\r\n" : "close\r\n");
904 if (!purple_http_headers_get(hdrs, "accept"))
905 g_string_append(h, "Accept: */*\r\n");
906 if (!purple_http_headers_get(hdrs, "accept-encoding"))
907 g_string_append(h, "Accept-Encoding: gzip, deflate\r\n");
909 if (!purple_http_headers_get(hdrs, "content-length") && (
910 req->contents_length > 0 ||
911 purple_http_request_is_method(req, "post")))
913 g_string_append_printf(h, "Content-Length: %u\r\n",
914 (guint) req->contents_length);
917 if (proxy_http)
918 g_string_append(h, "Proxy-Connection: close\r\n"); /* TEST: proxy+KeepAlive */
920 proxy_username = purple_proxy_info_get_username(proxy);
921 if (proxy_http && proxy_username != NULL && proxy_username[0] != '\0') {
922 gchar *proxy_auth, *ntlm_type1, *tmp;
923 int len;
925 proxy_password = purple_proxy_info_get_password(proxy);
926 if (proxy_password == NULL)
927 proxy_password = "";
929 tmp = g_strdup_printf("%s:%s", proxy_username, proxy_password);
930 len = strlen(tmp);
931 proxy_auth = g_base64_encode((const guchar *)tmp, len);
932 memset_zero(tmp, len);
933 g_free(tmp);
935 ntlm_type1 = purple_http_ntlm_gen_type1(purple_get_host_name(),
936 "");
938 g_string_append_printf(h, "Proxy-Authorization: Basic %s\r\n",
939 proxy_auth);
940 g_string_append_printf(h, "Proxy-Authorization: NTLM %s\r\n",
941 ntlm_type1);
942 g_string_append(h, "Proxy-Connection: close\r\n"); /* TEST: proxy+KeepAlive */
944 memset_zero(proxy_auth, strlen(proxy_auth));
945 g_free(proxy_auth);
946 g_free(ntlm_type1);
949 hdr = purple_http_headers_get_all(hdrs);
950 while (hdr) {
951 PurpleKeyValuePair *kvp = hdr->data;
952 hdr = g_list_next(hdr);
954 g_string_append_printf(h, "%s: %s\r\n",
955 kvp->key, (gchar*)kvp->value);
958 if (!purple_http_cookie_jar_is_empty(req->cookie_jar)) {
959 gchar * cookies = purple_http_cookie_jar_gen(req->cookie_jar);
960 g_string_append_printf(h, "Cookie: %s\r\n", cookies);
961 g_free(cookies);
964 g_string_append_printf(h, "\r\n");
966 if (purple_debug_is_unsafe() && purple_debug_is_verbose()) {
967 purple_debug_misc("http", "Generated request headers:\n%s",
968 h->str);
972 static gboolean _purple_http_recv_headers(PurpleHttpConnection *hc,
973 const gchar *buf, int len)
975 gchar *eol, *delim;
977 if (hc->headers_got) {
978 purple_debug_error("http", "Headers already got\n");
979 _purple_http_error(hc, _("Error parsing HTTP"));
980 return FALSE;
983 g_string_append_len(hc->response_buffer, buf, len);
984 if (hc->response_buffer->len > PURPLE_HTTP_MAX_RECV_BUFFER_LEN) {
985 purple_debug_error("http",
986 "Buffer too big when parsing headers\n");
987 _purple_http_error(hc, _("Error parsing HTTP"));
988 return FALSE;
991 while ((eol = strstr(hc->response_buffer->str, "\r\n"))
992 != NULL)
994 gchar *hdrline = hc->response_buffer->str;
995 int hdrline_len = eol - hdrline;
997 hdrline[hdrline_len] = '\0';
999 if (hdrline[0] == '\0') {
1000 if (!hc->main_header_got) {
1001 if (purple_debug_is_verbose() &&
1002 hc->is_keepalive)
1004 purple_debug_misc("http", "Got keep-"
1005 "alive terminator from previous"
1006 " request\n");
1007 } else {
1008 purple_debug_warning("http", "Got empty"
1009 " line at the beginning - this "
1010 "may be a HTTP server quirk\n");
1012 } else /* hc->main_header_got */ {
1013 hc->headers_got = TRUE;
1014 if (purple_debug_is_verbose()) {
1015 purple_debug_misc("http", "Got headers "
1016 "end\n");
1019 } else if (!hc->main_header_got) {
1020 hc->main_header_got = TRUE;
1021 delim = strchr(hdrline, ' ');
1022 if (delim == NULL || 1 != sscanf(delim + 1, "%d",
1023 &hc->response->code))
1025 purple_debug_warning("http",
1026 "Invalid response code\n");
1027 _purple_http_error(hc, _("Error parsing HTTP"));
1028 return FALSE;
1030 if (purple_debug_is_verbose())
1031 purple_debug_misc("http",
1032 "Got main header with code %d\n",
1033 hc->response->code);
1034 } else {
1035 if (purple_debug_is_verbose() &&
1036 purple_debug_is_unsafe())
1037 purple_debug_misc("http", "Got header: %s\n",
1038 hdrline);
1039 delim = strchr(hdrline, ':');
1040 if (delim == NULL || delim == hdrline) {
1041 purple_debug_warning("http",
1042 "Bad header delimiter\n");
1043 _purple_http_error(hc, _("Error parsing HTTP"));
1044 return FALSE;
1046 *delim++ = '\0';
1047 while (*delim == ' ')
1048 delim++;
1050 purple_http_headers_add(hc->response->headers, hdrline, delim);
1053 g_string_erase(hc->response_buffer, 0, hdrline_len + 2);
1054 if (hc->headers_got)
1055 break;
1057 return TRUE;
1060 static gboolean _purple_http_recv_body_data(PurpleHttpConnection *hc,
1061 const gchar *buf, int len)
1063 GString *decompressed = NULL;
1065 if (hc->length_expected >= 0 &&
1066 len + hc->length_got > (guint)hc->length_expected)
1068 len = hc->length_expected - hc->length_got;
1071 hc->length_got += len;
1073 if (hc->gz_stream != NULL) {
1074 decompressed = purple_http_gz_put(hc->gz_stream, buf, len);
1075 if (decompressed == NULL) {
1076 _purple_http_error(hc,
1077 _("Error while decompressing data"));
1078 return FALSE;
1080 buf = decompressed->str;
1081 len = decompressed->len;
1084 g_assert(hc->request->max_length <=
1085 PURPLE_HTTP_REQUEST_HARD_MAX_LENGTH);
1086 if (hc->length_got_decompressed + len > hc->request->max_length) {
1087 purple_debug_warning("http",
1088 "Maximum length exceeded, truncating\n");
1089 len = hc->request->max_length - hc->length_got_decompressed;
1090 hc->length_expected = hc->length_got;
1092 hc->length_got_decompressed += len;
1094 if (len == 0) {
1095 if (decompressed != NULL)
1096 g_string_free(decompressed, TRUE);
1097 return TRUE;
1100 if (hc->request->response_writer != NULL) {
1101 gboolean succ;
1102 succ = hc->request->response_writer(hc, hc->response, buf,
1103 hc->length_got_decompressed, len,
1104 hc->request->response_writer_data);
1105 if (!succ) {
1106 if (decompressed != NULL)
1107 g_string_free(decompressed, TRUE);
1108 purple_debug_error("http",
1109 "Cannot write using callback\n");
1110 _purple_http_error(hc,
1111 _("Error handling retrieved data"));
1112 return FALSE;
1114 } else {
1115 if (hc->response->contents == NULL)
1116 hc->response->contents = g_string_new("");
1117 g_string_append_len(hc->response->contents, buf, len);
1120 if (decompressed != NULL)
1121 g_string_free(decompressed, TRUE);
1123 purple_http_conn_notify_progress_watcher(hc);
1124 return TRUE;
1127 static gboolean _purple_http_recv_body_chunked(PurpleHttpConnection *hc,
1128 const gchar *buf, int len)
1130 gchar *eol, *line;
1131 int line_len;
1133 if (hc->chunks_done)
1134 return FALSE;
1135 if (!hc->response_buffer)
1136 hc->response_buffer = g_string_new("");
1138 g_string_append_len(hc->response_buffer, buf, len);
1139 if (hc->response_buffer->len > PURPLE_HTTP_MAX_RECV_BUFFER_LEN) {
1140 purple_debug_error("http",
1141 "Buffer too big when searching for chunk\n");
1142 _purple_http_error(hc, _("Error parsing HTTP"));
1143 return FALSE;
1146 while (hc->response_buffer->len > 0) {
1147 if (hc->in_chunk) {
1148 int got_now = hc->response_buffer->len;
1149 if (hc->chunk_got + got_now > hc->chunk_length)
1150 got_now = hc->chunk_length - hc->chunk_got;
1151 hc->chunk_got += got_now;
1153 if (!_purple_http_recv_body_data(hc,
1154 hc->response_buffer->str, got_now))
1155 return FALSE;
1157 g_string_erase(hc->response_buffer, 0, got_now);
1158 hc->in_chunk = (hc->chunk_got < hc->chunk_length);
1160 continue;
1163 line = hc->response_buffer->str;
1164 eol = strstr(line, "\r\n");
1165 if (eol == line) {
1166 g_string_erase(hc->response_buffer, 0, 2);
1167 line = hc->response_buffer->str;
1168 eol = strstr(line, "\r\n");
1170 if (eol == NULL) {
1171 /* waiting for more data (unlikely, but possible) */
1172 if (hc->response_buffer->len > 20) {
1173 purple_debug_warning("http", "Chunk length not "
1174 "found (buffer too large)\n");
1175 _purple_http_error(hc, _("Error parsing HTTP"));
1176 return FALSE;
1178 return TRUE;
1180 line_len = eol - line;
1182 if (1 != sscanf(line, "%x", &hc->chunk_length)) {
1183 if (purple_debug_is_unsafe())
1184 purple_debug_warning("http",
1185 "Chunk length not found in [%s]\n",
1186 line);
1187 else
1188 purple_debug_warning("http",
1189 "Chunk length not found\n");
1190 _purple_http_error(hc, _("Error parsing HTTP"));
1191 return FALSE;
1193 hc->chunk_got = 0;
1194 hc->in_chunk = TRUE;
1196 if (purple_debug_is_verbose())
1197 purple_debug_misc("http", "Found chunk of length %d\n", hc->chunk_length);
1199 g_string_erase(hc->response_buffer, 0, line_len + 2);
1201 if (hc->chunk_length == 0) {
1202 hc->chunks_done = TRUE;
1203 hc->in_chunk = FALSE;
1204 return TRUE;
1208 return TRUE;
1211 static gboolean _purple_http_recv_body(PurpleHttpConnection *hc,
1212 const gchar *buf, int len)
1214 if (hc->is_chunked)
1215 return _purple_http_recv_body_chunked(hc, buf, len);
1217 return _purple_http_recv_body_data(hc, buf, len);
1220 static gboolean _purple_http_recv_loopbody(PurpleHttpConnection *hc)
1222 int len;
1223 gchar buf[4096];
1224 gboolean got_anything;
1225 GError *error = NULL;
1227 len = g_pollable_input_stream_read_nonblocking(
1228 G_POLLABLE_INPUT_STREAM(
1229 g_io_stream_get_input_stream(
1230 G_IO_STREAM(hc->socket->conn))),
1231 buf, sizeof(buf), hc->socket->cancellable,
1232 &error);
1233 got_anything = (len > 0);
1235 if (len < 0 && (g_error_matches(error,
1236 G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) ||
1237 g_error_matches(error,
1238 G_IO_ERROR, G_IO_ERROR_CANCELLED))) {
1239 g_clear_error(&error);
1240 return FALSE;
1243 if (len < 0) {
1244 _purple_http_error(hc, _("Error reading from %s: %s"),
1245 hc->url->host, error->message);
1246 g_clear_error(&error);
1247 return FALSE;
1250 /* EOF */
1251 if (len == 0) {
1252 if (hc->request->max_length == 0) {
1253 /* It's definitely YHttpServer quirk. */
1254 purple_debug_warning("http", "Got EOF, but no data was "
1255 "expected (this may be a server quirk)\n");
1256 hc->length_expected = hc->length_got;
1258 if (hc->length_expected >= 0 &&
1259 hc->length_got < (guint)hc->length_expected)
1261 purple_debug_warning("http", "No more data while reading"
1262 " contents\n");
1263 _purple_http_error(hc, _("Error parsing HTTP"));
1264 return FALSE;
1266 if (hc->headers_got)
1267 hc->length_expected = hc->length_got;
1268 else if (hc->length_got == 0 && hc->socket->use_count > 1) {
1269 purple_debug_info("http", "Keep-alive connection "
1270 "expired (when reading), retrying...\n");
1271 purple_http_conn_retry(hc);
1272 return FALSE;
1273 } else {
1274 const gchar *server = purple_http_headers_get(
1275 hc->response->headers, "Server");
1276 if (server &&
1277 g_ascii_strcasecmp(server, "YHttpServer") == 0)
1279 purple_debug_warning("http", "No more data "
1280 "while parsing headers (YHttpServer "
1281 "quirk)\n");
1282 hc->headers_got = TRUE;
1283 hc->length_expected = hc->length_got = 0;
1284 hc->length_got_decompressed = 0;
1285 } else {
1286 purple_debug_warning("http", "No more data "
1287 "while parsing headers\n");
1288 _purple_http_error(hc, _("Error parsing HTTP"));
1289 return FALSE;
1294 if (!hc->headers_got && len > 0) {
1295 if (!_purple_http_recv_headers(hc, buf, len))
1296 return FALSE;
1297 len = 0;
1298 if (hc->headers_got) {
1299 gboolean is_gzip, is_deflate;
1300 if (!purple_http_headers_get_int(hc->response->headers,
1301 "Content-Length", &hc->length_expected))
1302 hc->length_expected = -1;
1303 hc->is_chunked = (purple_http_headers_match(
1304 hc->response->headers,
1305 "Transfer-Encoding", "chunked"));
1306 is_gzip = purple_http_headers_match(
1307 hc->response->headers, "Content-Encoding",
1308 "gzip");
1309 is_deflate = purple_http_headers_match(
1310 hc->response->headers, "Content-Encoding",
1311 "deflate");
1312 if (is_gzip || is_deflate) {
1313 hc->gz_stream = purple_http_gz_new(
1314 hc->request->max_length + 1,
1315 is_deflate);
1318 if (hc->headers_got && hc->response_buffer &&
1319 hc->response_buffer->len > 0)
1321 int buffer_len = hc->response_buffer->len;
1322 gchar *buffer = g_string_free(hc->response_buffer, FALSE);
1323 hc->response_buffer = NULL;
1324 if (!_purple_http_recv_body(hc, buffer, buffer_len))
1326 g_free(buffer);
1327 return FALSE;
1329 g_free(buffer);
1331 if (!hc->headers_got)
1332 return got_anything;
1335 if (len > 0) {
1336 if (!_purple_http_recv_body(hc, buf, len))
1337 return FALSE;
1340 if (hc->is_chunked && hc->chunks_done && hc->length_expected < 0)
1341 hc->length_expected = hc->length_got;
1343 if (hc->length_expected >= 0 &&
1344 hc->length_got >= (guint)hc->length_expected)
1346 const gchar *redirect;
1348 if (hc->is_chunked && !hc->chunks_done) {
1349 if (len == 0) {
1350 _purple_http_error(hc, _("Chunked connection terminated"));
1351 return FALSE;
1353 if (purple_debug_is_verbose()) {
1354 purple_debug_misc("http",
1355 "I need the terminating empty chunk\n");
1357 return TRUE;
1360 if (!hc->headers_got) {
1361 hc->response->code = 0;
1362 purple_debug_warning("http", "No headers got\n");
1363 _purple_http_error(hc, _("Error parsing HTTP"));
1364 return FALSE;
1367 if (purple_debug_is_unsafe() && purple_debug_is_verbose()) {
1368 gchar *hdrs = purple_http_headers_dump(
1369 hc->response->headers);
1370 purple_debug_misc("http", "Got response headers: %s\n",
1371 hdrs);
1372 g_free(hdrs);
1375 purple_http_cookie_jar_parse(hc->request->cookie_jar,
1376 purple_http_headers_get_all_by_name(
1377 hc->response->headers, "Set-Cookie"));
1379 if (purple_debug_is_unsafe() && purple_debug_is_verbose() &&
1380 !purple_http_cookie_jar_is_empty(
1381 hc->request->cookie_jar))
1383 gchar *cookies = purple_http_cookie_jar_dump(
1384 hc->request->cookie_jar);
1385 purple_debug_misc("http", "Cookies: %s\n", cookies);
1386 g_free(cookies);
1389 if (hc->response->code == 407) {
1390 _purple_http_error(hc, _("Invalid proxy credentials"));
1391 return FALSE;
1394 redirect = purple_http_headers_get(hc->response->headers,
1395 "location");
1396 if (redirect && (hc->request->max_redirects == -1 ||
1397 hc->request->max_redirects > hc->redirects_count))
1399 PurpleHttpURL *url = purple_http_url_parse(redirect);
1401 hc->redirects_count++;
1403 if (!url) {
1404 if (purple_debug_is_unsafe())
1405 purple_debug_warning("http",
1406 "Invalid redirect to %s\n",
1407 redirect);
1408 else
1409 purple_debug_warning("http",
1410 "Invalid redirect\n");
1411 _purple_http_error(hc, _("Error parsing HTTP"));
1414 purple_http_url_relative(hc->url, url);
1415 purple_http_url_free(url);
1417 _purple_http_reconnect(hc);
1418 return FALSE;
1421 _purple_http_disconnect(hc, TRUE);
1422 purple_http_connection_terminate(hc);
1423 return FALSE;
1426 return got_anything;
1429 static gboolean _purple_http_recv(GObject *source, gpointer _hc)
1431 PurpleHttpConnection *hc = _hc;
1433 while (_purple_http_recv_loopbody(hc));
1435 return G_SOURCE_CONTINUE;
1438 static void _purple_http_send_got_data(PurpleHttpConnection *hc,
1439 gboolean success, gboolean eof, size_t stored)
1441 int estimated_length;
1443 g_return_if_fail(hc != NULL);
1445 if (!success) {
1446 _purple_http_error(hc, _("Error requesting data to write"));
1447 return;
1450 hc->contents_reader_requested = FALSE;
1451 g_string_set_size(hc->contents_reader_buffer, stored);
1452 if (!eof)
1453 return;
1455 estimated_length = hc->request_contents_written + stored;
1457 if (hc->request->contents_length != -1 &&
1458 hc->request->contents_length != estimated_length)
1460 purple_debug_warning("http",
1461 "Invalid amount of data has been written\n");
1463 hc->request->contents_length = estimated_length;
1466 static gboolean _purple_http_send(GObject *source, gpointer _hc)
1468 PurpleHttpConnection *hc = _hc;
1469 int written, write_len;
1470 const gchar *write_from;
1471 gboolean writing_headers;
1472 GError *error = NULL;
1473 GSource *gsource;
1475 /* Waiting for data. This could be written more efficiently, by removing
1476 * (and later, adding) hs->inpa. */
1477 if (hc->contents_reader_requested)
1478 return G_SOURCE_CONTINUE;
1480 _purple_http_gen_headers(hc);
1482 writing_headers =
1483 (hc->request_header_written < hc->request_header->len);
1484 if (writing_headers) {
1485 write_from = hc->request_header->str +
1486 hc->request_header_written;
1487 write_len = hc->request_header->len -
1488 hc->request_header_written;
1489 } else if (hc->request->contents_reader) {
1490 if (hc->contents_reader_requested)
1491 return G_SOURCE_CONTINUE; /* waiting for data */
1492 if (!hc->contents_reader_buffer)
1493 hc->contents_reader_buffer = g_string_new("");
1494 if (hc->contents_reader_buffer->len == 0) {
1495 hc->contents_reader_requested = TRUE;
1496 g_string_set_size(hc->contents_reader_buffer,
1497 PURPLE_HTTP_MAX_READ_BUFFER_LEN);
1498 hc->request->contents_reader(hc,
1499 hc->contents_reader_buffer->str,
1500 hc->request_contents_written,
1501 PURPLE_HTTP_MAX_READ_BUFFER_LEN,
1502 hc->request->contents_reader_data,
1503 _purple_http_send_got_data);
1504 return G_SOURCE_CONTINUE;
1506 write_from = hc->contents_reader_buffer->str;
1507 write_len = hc->contents_reader_buffer->len;
1508 } else {
1509 write_from = hc->request->contents +
1510 hc->request_contents_written;
1511 write_len = hc->request->contents_length -
1512 hc->request_contents_written;
1515 if (write_len == 0) {
1516 purple_debug_warning("http", "Nothing to write\n");
1517 written = 0;
1518 } else {
1519 written = g_pollable_output_stream_write_nonblocking(
1520 G_POLLABLE_OUTPUT_STREAM(
1521 g_io_stream_get_output_stream(
1522 G_IO_STREAM(hc->socket->conn))),
1523 write_from, write_len, hc->socket->cancellable,
1524 &error);
1527 if (written < 0 && (g_error_matches(error,
1528 G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) ||
1529 g_error_matches(error,
1530 G_IO_ERROR, G_IO_ERROR_CANCELLED))) {
1531 g_clear_error(&error);
1532 return G_SOURCE_CONTINUE;
1535 if (written < 0) {
1536 if (hc->request_header_written == 0 &&
1537 hc->socket->use_count > 1)
1539 purple_debug_info("http", "Keep-alive connection "
1540 "expired (when writing), retrying...\n");
1541 purple_http_conn_retry(hc);
1542 } else {
1543 _purple_http_error(hc, _("Error writing to %s: %s"),
1544 hc->url->host, error->message);
1547 g_clear_error(&error);
1548 return G_SOURCE_CONTINUE;
1551 if (writing_headers) {
1552 hc->request_header_written += written;
1553 purple_http_conn_notify_progress_watcher(hc);
1554 if (hc->request_header_written < hc->request_header->len)
1555 return G_SOURCE_CONTINUE;
1556 if (hc->request->contents_length > 0)
1557 return G_SOURCE_CONTINUE;
1558 } else {
1559 hc->request_contents_written += written;
1560 purple_http_conn_notify_progress_watcher(hc);
1561 if (hc->contents_reader_buffer)
1562 g_string_erase(hc->contents_reader_buffer, 0, written);
1563 if (hc->request->contents_length > 0 &&
1564 hc->request_contents_written <
1565 (guint)hc->request->contents_length)
1567 return G_SOURCE_CONTINUE;
1571 /* request is completely written, let's read the response */
1572 hc->is_reading = TRUE;
1573 gsource = g_pollable_input_stream_create_source(
1574 G_POLLABLE_INPUT_STREAM(
1575 g_io_stream_get_input_stream(
1576 G_IO_STREAM(hc->socket->conn))),
1577 NULL);
1578 g_source_set_callback(gsource,
1579 (GSourceFunc)_purple_http_recv, hc, NULL);
1580 hc->socket->input_source = g_source_attach(gsource, NULL);
1581 g_source_unref(gsource);
1583 hc->socket->output_source = 0;
1584 return G_SOURCE_REMOVE;
1587 static void _purple_http_disconnect(PurpleHttpConnection *hc,
1588 gboolean is_graceful)
1590 g_return_if_fail(hc != NULL);
1592 if (hc->request_header)
1593 g_string_free(hc->request_header, TRUE);
1594 hc->request_header = NULL;
1596 if (hc->response_buffer)
1597 g_string_free(hc->response_buffer, TRUE);
1598 hc->response_buffer = NULL;
1600 if (hc->gz_stream)
1601 purple_http_gz_free(hc->gz_stream);
1602 hc->gz_stream = NULL;
1604 if (hc->socket_request)
1605 purple_http_keepalive_pool_request_cancel(hc->socket_request);
1606 else {
1607 purple_http_keepalive_pool_release(hc->socket, !is_graceful);
1608 hc->socket = NULL;
1612 static void
1613 _purple_http_connected(PurpleHttpSocket *hs, const gchar *error, gpointer _hc)
1615 PurpleHttpConnection *hc = _hc;
1616 GSource *source;
1618 hc->socket_request = NULL;
1619 hc->socket = hs;
1621 if (error != NULL) {
1622 _purple_http_error(hc, _("Unable to connect to %s: %s"),
1623 hc->url->host, error);
1624 return;
1627 source = g_pollable_output_stream_create_source(
1628 G_POLLABLE_OUTPUT_STREAM(
1629 g_io_stream_get_output_stream(G_IO_STREAM(hs->conn))),
1630 NULL);
1631 g_source_set_callback(source,
1632 (GSourceFunc)_purple_http_send, hc, NULL);
1633 hc->socket->output_source = g_source_attach(source, NULL);
1634 g_source_unref(source);
1637 static gboolean _purple_http_reconnect(PurpleHttpConnection *hc)
1639 PurpleHttpURL *url;
1640 gboolean is_ssl = FALSE;
1642 g_return_val_if_fail(hc != NULL, FALSE);
1643 g_return_val_if_fail(hc->url != NULL, FALSE);
1645 _purple_http_disconnect(hc, TRUE);
1647 if (purple_debug_is_verbose()) {
1648 if (purple_debug_is_unsafe()) {
1649 gchar *urlp = purple_http_url_print(hc->url);
1650 purple_debug_misc("http", "Connecting to %s...\n", urlp);
1651 g_free(urlp);
1652 } else
1653 purple_debug_misc("http", "Connecting to %s...\n",
1654 hc->url->host);
1657 url = hc->url;
1658 if (g_strcmp0(url->protocol, "") == 0 ||
1659 g_ascii_strcasecmp(url->protocol, "http") == 0)
1661 /* do nothing */
1662 } else if (g_ascii_strcasecmp(url->protocol, "https") == 0) {
1663 is_ssl = TRUE;
1664 } else {
1665 _purple_http_error(hc, _("Unsupported protocol: %s"),
1666 url->protocol);
1667 return FALSE;
1670 if (hc->request->keepalive_pool != NULL) {
1671 hc->socket_request = purple_http_keepalive_pool_request(
1672 hc->request->keepalive_pool, hc->gc, url->host,
1673 url->port, is_ssl, _purple_http_connected, hc);
1674 } else {
1675 hc->socket = purple_http_socket_connect_new(hc->gc, url->host,
1676 url->port, is_ssl, _purple_http_connected, hc);
1679 if (hc->socket_request == NULL && hc->socket == NULL) {
1680 _purple_http_error(hc, _("Unable to connect to %s"), url->host);
1681 return FALSE;
1684 purple_http_headers_free(hc->response->headers);
1685 hc->response->headers = purple_http_headers_new();
1686 hc->response_buffer = g_string_new("");
1687 hc->main_header_got = FALSE;
1688 hc->headers_got = FALSE;
1689 if (hc->response->contents != NULL)
1690 g_string_free(hc->response->contents, TRUE);
1691 hc->response->contents = NULL;
1692 hc->length_got = 0;
1693 hc->length_got_decompressed = 0;
1694 hc->length_expected = -1;
1695 hc->is_chunked = FALSE;
1696 hc->in_chunk = FALSE;
1697 hc->chunks_done = FALSE;
1699 purple_http_conn_notify_progress_watcher(hc);
1701 return TRUE;
1704 /*** Performing HTTP requests *************************************************/
1706 static gboolean purple_http_request_timeout(gpointer _hc)
1708 PurpleHttpConnection *hc = _hc;
1710 purple_debug_warning("http", "Timeout reached for request %p\n", hc);
1712 purple_http_conn_cancel(hc);
1714 return FALSE;
1717 PurpleHttpConnection * purple_http_get(PurpleConnection *gc,
1718 PurpleHttpCallback callback, gpointer user_data, const gchar *url)
1720 PurpleHttpRequest *request;
1721 PurpleHttpConnection *hc;
1723 g_return_val_if_fail(url != NULL, NULL);
1725 request = purple_http_request_new(url);
1726 hc = purple_http_request(gc, request, callback, user_data);
1727 purple_http_request_unref(request);
1729 return hc;
1732 PurpleHttpConnection * purple_http_get_printf(PurpleConnection *gc,
1733 PurpleHttpCallback callback, gpointer user_data,
1734 const gchar *format, ...)
1736 va_list args;
1737 gchar *value;
1738 PurpleHttpConnection *ret;
1740 g_return_val_if_fail(format != NULL, NULL);
1742 va_start(args, format);
1743 value = g_strdup_vprintf(format, args);
1744 va_end(args);
1746 ret = purple_http_get(gc, callback, user_data, value);
1747 g_free(value);
1749 return ret;
1752 PurpleHttpConnection * purple_http_request(PurpleConnection *gc,
1753 PurpleHttpRequest *request, PurpleHttpCallback callback,
1754 gpointer user_data)
1756 PurpleHttpConnection *hc;
1758 g_return_val_if_fail(request != NULL, NULL);
1760 if (request->url == NULL) {
1761 purple_debug_error("http", "Cannot perform new request - "
1762 "URL is not set\n");
1763 return NULL;
1766 if (g_hash_table_lookup(purple_http_cancelling_gc, gc)) {
1767 purple_debug_warning("http", "Cannot perform another HTTP "
1768 "request while cancelling all related with this "
1769 "PurpleConnection\n");
1770 return NULL;
1773 hc = purple_http_connection_new(request, gc);
1774 hc->callback = callback;
1775 hc->user_data = user_data;
1777 hc->url = purple_http_url_parse(request->url);
1779 if (purple_debug_is_unsafe())
1780 purple_debug_misc("http", "Performing new request %p for %s.\n",
1781 hc, request->url);
1782 else
1783 purple_debug_misc("http", "Performing new request %p to %s.\n",
1784 hc, hc->url ? hc->url->host : NULL);
1786 if (!hc->url || hc->url->host == NULL || hc->url->host[0] == '\0') {
1787 purple_debug_error("http", "Invalid URL requested.\n");
1788 purple_http_connection_terminate(hc);
1789 return NULL;
1792 _purple_http_reconnect(hc);
1794 hc->timeout_handle = g_timeout_add_seconds(request->timeout,
1795 purple_http_request_timeout, hc);
1797 return hc;
1800 /*** HTTP connection API ******************************************************/
1802 static void purple_http_connection_free(PurpleHttpConnection *hc);
1803 static gboolean purple_http_conn_notify_progress_watcher_timeout(gpointer _hc);
1805 static PurpleHttpConnection * purple_http_connection_new(
1806 PurpleHttpRequest *request, PurpleConnection *gc)
1808 PurpleHttpConnection *hc = g_new0(PurpleHttpConnection, 1);
1810 g_assert(request != NULL);
1812 hc->request = request;
1813 purple_http_request_ref(request);
1814 hc->response = purple_http_response_new();
1815 hc->is_keepalive = (request->keepalive_pool != NULL);
1817 hc->link_global = purple_http_hc_list =
1818 g_list_prepend(purple_http_hc_list, hc);
1819 g_hash_table_insert(purple_http_hc_by_ptr, hc, hc->link_global);
1820 if (gc) {
1821 GList *gc_list = g_hash_table_lookup(purple_http_hc_by_gc, gc);
1822 g_hash_table_steal(purple_http_hc_by_gc, gc);
1823 hc->link_gc = gc_list = g_list_prepend(gc_list, hc);
1824 g_hash_table_insert(purple_http_hc_by_gc, gc, gc_list);
1825 hc->gc = gc;
1828 return hc;
1831 static void purple_http_connection_free(PurpleHttpConnection *hc)
1833 if (hc->timeout_handle)
1834 g_source_remove(hc->timeout_handle);
1835 if (hc->watcher_delayed_handle)
1836 g_source_remove(hc->watcher_delayed_handle);
1838 if (hc->connection_set != NULL)
1839 purple_http_connection_set_remove(hc->connection_set, hc);
1841 purple_http_url_free(hc->url);
1842 purple_http_request_unref(hc->request);
1843 purple_http_response_free(hc->response);
1845 if (hc->contents_reader_buffer)
1846 g_string_free(hc->contents_reader_buffer, TRUE);
1847 purple_http_gz_free(hc->gz_stream);
1849 if (hc->request_header)
1850 g_string_free(hc->request_header, TRUE);
1852 purple_http_hc_list = g_list_delete_link(purple_http_hc_list,
1853 hc->link_global);
1854 g_hash_table_remove(purple_http_hc_by_ptr, hc);
1855 if (hc->gc) {
1856 GList *gc_list, *gc_list_new;
1857 gc_list = g_hash_table_lookup(purple_http_hc_by_gc, hc->gc);
1858 g_assert(gc_list != NULL);
1860 gc_list_new = g_list_delete_link(gc_list, hc->link_gc);
1861 if (gc_list != gc_list_new) {
1862 g_hash_table_steal(purple_http_hc_by_gc, hc->gc);
1863 if (gc_list_new)
1864 g_hash_table_insert(purple_http_hc_by_gc,
1865 hc->gc, gc_list_new);
1869 g_free(hc);
1872 /* call callback and do the cleanup */
1873 static void purple_http_connection_terminate(PurpleHttpConnection *hc)
1875 g_return_if_fail(hc != NULL);
1877 purple_debug_misc("http", "Request %p performed %s.\n", hc,
1878 purple_http_response_is_successful(hc->response) ?
1879 "successfully" : "without success");
1881 if (hc->callback)
1882 hc->callback(hc, hc->response, hc->user_data);
1884 purple_http_connection_free(hc);
1887 void purple_http_conn_cancel(PurpleHttpConnection *http_conn)
1889 if (http_conn == NULL)
1890 return;
1892 if (http_conn->is_cancelling)
1893 return;
1894 http_conn->is_cancelling = TRUE;
1896 if (purple_debug_is_verbose()) {
1897 purple_debug_misc("http", "Cancelling connection %p...\n",
1898 http_conn);
1901 if (http_conn->response != NULL) {
1902 http_conn->response->code = 0;
1904 _purple_http_disconnect(http_conn, FALSE);
1905 purple_http_connection_terminate(http_conn);
1908 static void
1909 purple_http_conn_retry(PurpleHttpConnection *http_conn)
1911 if (http_conn == NULL)
1912 return;
1914 purple_debug_info("http", "Retrying connection %p...\n", http_conn);
1916 if (http_conn->response != NULL) {
1917 http_conn->response->code = 0;
1919 _purple_http_disconnect(http_conn, FALSE);
1920 _purple_http_reconnect(http_conn);
1923 void purple_http_conn_cancel_all(PurpleConnection *gc)
1925 GList *gc_list;
1927 if (purple_debug_is_verbose()) {
1928 purple_debug_misc("http", "Cancelling all running HTTP "
1929 "connections\n");
1932 gc_list = g_hash_table_lookup(purple_http_hc_by_gc, gc);
1934 g_hash_table_insert(purple_http_cancelling_gc, gc, GINT_TO_POINTER(1));
1936 while (gc_list) {
1937 PurpleHttpConnection *hc = gc_list->data;
1938 gc_list = g_list_next(gc_list);
1939 purple_http_conn_cancel(hc);
1942 g_hash_table_remove(purple_http_cancelling_gc, gc);
1944 if (NULL != g_hash_table_lookup(purple_http_hc_by_gc, gc))
1945 purple_debug_fatal("http", "Couldn't cancel all connections "
1946 "related to gc=%p (it shouldn't happen)\n", gc);
1949 gboolean purple_http_conn_is_running(PurpleHttpConnection *http_conn)
1951 if (http_conn == NULL)
1952 return FALSE;
1953 return (NULL != g_hash_table_lookup(purple_http_hc_by_ptr, http_conn));
1956 PurpleHttpRequest * purple_http_conn_get_request(PurpleHttpConnection *http_conn)
1958 g_return_val_if_fail(http_conn != NULL, NULL);
1960 return http_conn->request;
1963 PurpleHttpCookieJar * purple_http_conn_get_cookie_jar(
1964 PurpleHttpConnection *http_conn)
1966 return purple_http_request_get_cookie_jar(purple_http_conn_get_request(
1967 http_conn));
1970 PurpleConnection * purple_http_conn_get_purple_connection(
1971 PurpleHttpConnection *http_conn)
1973 g_return_val_if_fail(http_conn != NULL, NULL);
1975 return http_conn->gc;
1978 void purple_http_conn_set_progress_watcher(PurpleHttpConnection *http_conn,
1979 PurpleHttpProgressWatcher watcher, gpointer user_data,
1980 gint interval_threshold)
1982 g_return_if_fail(http_conn != NULL);
1984 if (interval_threshold < 0) {
1985 interval_threshold =
1986 PURPLE_HTTP_PROGRESS_WATCHER_DEFAULT_INTERVAL;
1989 http_conn->watcher = watcher;
1990 http_conn->watcher_user_data = user_data;
1991 http_conn->watcher_interval_threshold = interval_threshold;
1994 static void purple_http_conn_notify_progress_watcher(
1995 PurpleHttpConnection *hc)
1997 gint64 now;
1998 gboolean reading_state;
1999 int processed, total;
2001 g_return_if_fail(hc != NULL);
2003 if (hc->watcher == NULL)
2004 return;
2006 reading_state = hc->is_reading;
2007 if (reading_state) {
2008 total = hc->length_expected;
2009 processed = hc->length_got;
2010 } else {
2011 total = hc->request->contents_length;
2012 processed = hc->request_contents_written;
2013 if (total == 0)
2014 total = -1;
2016 if (total != -1 && total < processed) {
2017 purple_debug_warning("http", "Processed too much\n");
2018 total = processed;
2021 now = g_get_monotonic_time();
2022 if (hc->watcher_last_call + hc->watcher_interval_threshold
2023 > now && processed != total)
2025 if (hc->watcher_delayed_handle)
2026 return;
2027 hc->watcher_delayed_handle = g_timeout_add_seconds(
2028 1 + hc->watcher_interval_threshold / 1000000,
2029 purple_http_conn_notify_progress_watcher_timeout, hc);
2030 return;
2033 if (hc->watcher_delayed_handle)
2034 g_source_remove(hc->watcher_delayed_handle);
2035 hc->watcher_delayed_handle = 0;
2037 hc->watcher_last_call = now;
2038 hc->watcher(hc, reading_state, processed, total, hc->watcher_user_data);
2041 static gboolean purple_http_conn_notify_progress_watcher_timeout(gpointer _hc)
2043 PurpleHttpConnection *hc = _hc;
2045 purple_http_conn_notify_progress_watcher(hc);
2047 return FALSE;
2050 /*** Cookie jar API ***********************************************************/
2052 static PurpleHttpCookie * purple_http_cookie_new(const gchar *value);
2053 void purple_http_cookie_free(PurpleHttpCookie *cookie);
2055 static void purple_http_cookie_jar_set_ext(PurpleHttpCookieJar *cookie_jar,
2056 const gchar *name, const gchar *value, time_t expires);
2058 static PurpleHttpCookie * purple_http_cookie_new(const gchar *value)
2060 PurpleHttpCookie *cookie = g_new0(PurpleHttpCookie, 1);
2062 cookie->value = g_strdup(value);
2063 cookie->expires = -1;
2065 return cookie;
2068 void purple_http_cookie_free(PurpleHttpCookie *cookie)
2070 g_free(cookie->value);
2071 g_free(cookie);
2074 void purple_http_cookie_jar_free(PurpleHttpCookieJar *cookie_jar);
2076 PurpleHttpCookieJar * purple_http_cookie_jar_new(void)
2078 PurpleHttpCookieJar *cjar = g_new0(PurpleHttpCookieJar, 1);
2080 cjar->ref_count = 1;
2081 cjar->tab = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
2082 (GDestroyNotify)purple_http_cookie_free);
2084 return cjar;
2087 void purple_http_cookie_jar_free(PurpleHttpCookieJar *cookie_jar)
2089 g_hash_table_destroy(cookie_jar->tab);
2090 g_free(cookie_jar);
2093 void purple_http_cookie_jar_ref(PurpleHttpCookieJar *cookie_jar)
2095 g_return_if_fail(cookie_jar != NULL);
2097 cookie_jar->ref_count++;
2100 PurpleHttpCookieJar * purple_http_cookie_jar_unref(
2101 PurpleHttpCookieJar *cookie_jar)
2103 if (cookie_jar == NULL)
2104 return NULL;
2106 g_return_val_if_fail(cookie_jar->ref_count > 0, NULL);
2108 cookie_jar->ref_count--;
2109 if (cookie_jar->ref_count > 0)
2110 return cookie_jar;
2112 purple_http_cookie_jar_free(cookie_jar);
2113 return NULL;
2116 static void purple_http_cookie_jar_parse(PurpleHttpCookieJar *cookie_jar,
2117 GList *values)
2119 values = g_list_first(values);
2120 while (values) {
2121 const gchar *cookie = values->data;
2122 const gchar *eqsign, *semicolon;
2123 gchar *name, *value;
2124 time_t expires = -1;
2125 values = g_list_next(values);
2127 eqsign = strchr(cookie, '=');
2128 semicolon = strchr(cookie, ';');
2130 if (eqsign == NULL || eqsign == cookie ||
2131 (semicolon != NULL && semicolon < eqsign))
2133 if (purple_debug_is_unsafe())
2134 purple_debug_warning("http",
2135 "Invalid cookie: [%s]\n", cookie);
2136 else
2137 purple_debug_warning("http", "Invalid cookie.");
2138 continue;
2141 name = g_strndup(cookie, eqsign - cookie);
2142 eqsign++;
2143 if (semicolon != NULL)
2144 value = g_strndup(eqsign, semicolon - eqsign);
2145 else
2146 value = g_strdup(eqsign);
2148 if (semicolon != NULL) {
2149 GMatchInfo *match_info;
2150 GRegex *re_expires = g_regex_new( /* XXX: make it static */
2151 "expires=([a-z0-9, :]+)",
2152 G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
2153 G_REGEX_MATCH_NOTEMPTY, NULL);
2155 g_regex_match(re_expires, semicolon, 0, &match_info);
2156 if (g_match_info_matches(match_info)) {
2157 gchar *expire_date =
2158 g_match_info_fetch(match_info, 1);
2159 expires = purple_http_rfc1123_to_time(
2160 expire_date);
2161 g_free(expire_date);
2163 g_match_info_free(match_info);
2165 g_regex_unref(re_expires);
2168 purple_http_cookie_jar_set_ext(cookie_jar, name, value, expires);
2170 g_free(name);
2171 g_free(value);
2175 static gchar * purple_http_cookie_jar_gen(PurpleHttpCookieJar *cookie_jar)
2177 GHashTableIter it;
2178 gchar *key;
2179 PurpleHttpCookie *cookie;
2180 GString *str;
2181 time_t now = time(NULL);
2183 g_return_val_if_fail(cookie_jar != NULL, NULL);
2185 str = g_string_new("");
2187 g_hash_table_iter_init(&it, cookie_jar->tab);
2188 while (g_hash_table_iter_next(&it, (gpointer*)&key,
2189 (gpointer*)&cookie))
2191 if (cookie->expires != -1 && cookie->expires != 0 && cookie->expires <= now)
2192 continue;
2193 g_string_append_printf(str, "%s=%s; ", key, cookie->value);
2196 if (str->len > 0)
2197 g_string_truncate(str, str->len - 2);
2198 return g_string_free(str, FALSE);
2201 void purple_http_cookie_jar_set(PurpleHttpCookieJar *cookie_jar,
2202 const gchar *name, const gchar *value)
2204 gchar *escaped_name = g_strdup(purple_url_encode(name));
2205 gchar *escaped_value = NULL;
2207 if (value) {
2208 escaped_value = g_strdup(purple_url_encode(value));
2211 purple_http_cookie_jar_set_ext(cookie_jar, escaped_name, escaped_value, -1);
2213 g_free(escaped_name);
2214 g_free(escaped_value);
2217 static void purple_http_cookie_jar_set_ext(PurpleHttpCookieJar *cookie_jar,
2218 const gchar *name, const gchar *value, time_t expires)
2220 g_return_if_fail(cookie_jar != NULL);
2221 g_return_if_fail(name != NULL);
2223 if (expires != -1 && expires != 0 && time(NULL) >= expires)
2224 value = NULL;
2226 if (value != NULL) {
2227 PurpleHttpCookie *cookie = purple_http_cookie_new(value);
2228 cookie->expires = expires;
2229 g_hash_table_insert(cookie_jar->tab, g_strdup(name), cookie);
2230 } else
2231 g_hash_table_remove(cookie_jar->tab, name);
2234 gchar * purple_http_cookie_jar_get(PurpleHttpCookieJar *cookie_jar,
2235 const gchar *name)
2237 PurpleHttpCookie *cookie;
2239 g_return_val_if_fail(cookie_jar != NULL, NULL);
2240 g_return_val_if_fail(name != NULL, NULL);
2242 cookie = g_hash_table_lookup(cookie_jar->tab, name);
2243 if (!cookie)
2244 return NULL;
2246 return g_strdup(purple_url_decode(cookie->value));
2249 gchar * purple_http_cookie_jar_dump(PurpleHttpCookieJar *cjar)
2251 GHashTableIter it;
2252 gchar *key;
2253 PurpleHttpCookie *cookie;
2254 GString *str = g_string_new("");
2256 g_hash_table_iter_init(&it, cjar->tab);
2257 while (g_hash_table_iter_next(&it, (gpointer*)&key, (gpointer*)&cookie))
2258 g_string_append_printf(str, "%s: %s (expires: %" G_GINT64_FORMAT
2259 ")\n", key, cookie->value, (gint64)cookie->expires);
2261 if (str->len > 0)
2262 g_string_truncate(str, str->len - 1);
2263 return g_string_free(str, FALSE);
2266 gboolean purple_http_cookie_jar_is_empty(PurpleHttpCookieJar *cookie_jar)
2268 g_return_val_if_fail(cookie_jar != NULL, TRUE);
2270 return g_hash_table_size(cookie_jar->tab) == 0;
2273 /*** HTTP Keep-Alive pool API *************************************************/
2275 static void
2276 purple_http_keepalive_host_process_queue(PurpleHttpKeepaliveHost *host);
2278 static void
2279 purple_http_keepalive_host_free(gpointer _host)
2281 PurpleHttpKeepaliveHost *host = _host;
2283 g_free(host->host);
2285 g_slist_free_full(host->queue,
2286 (GDestroyNotify)purple_http_keepalive_pool_request_cancel);
2287 g_slist_free_full(host->sockets,
2288 (GDestroyNotify)purple_http_socket_close_free);
2290 if (host->process_queue_timeout > 0) {
2291 g_source_remove(host->process_queue_timeout);
2292 host->process_queue_timeout = 0;
2296 g_free(host);
2299 PurpleHttpKeepalivePool *
2300 purple_http_keepalive_pool_new(void)
2302 PurpleHttpKeepalivePool *pool = g_new0(PurpleHttpKeepalivePool, 1);
2304 pool->ref_count = 1;
2305 pool->by_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
2306 purple_http_keepalive_host_free);
2308 return pool;
2311 static void
2312 purple_http_keepalive_pool_free(PurpleHttpKeepalivePool *pool)
2314 g_return_if_fail(pool != NULL);
2316 if (pool->is_destroying)
2317 return;
2318 pool->is_destroying = TRUE;
2319 g_hash_table_destroy(pool->by_hash);
2320 g_free(pool);
2323 void
2324 purple_http_keepalive_pool_ref(PurpleHttpKeepalivePool *pool)
2326 g_return_if_fail(pool != NULL);
2328 pool->ref_count++;
2331 PurpleHttpKeepalivePool *
2332 purple_http_keepalive_pool_unref(PurpleHttpKeepalivePool *pool)
2334 if (pool == NULL)
2335 return NULL;
2337 g_return_val_if_fail(pool->ref_count > 0, NULL);
2339 pool->ref_count--;
2340 if (pool->ref_count > 0)
2341 return pool;
2343 purple_http_keepalive_pool_free(pool);
2344 return NULL;
2347 static PurpleHttpKeepaliveRequest *
2348 purple_http_keepalive_pool_request(PurpleHttpKeepalivePool *pool,
2349 PurpleConnection *gc, const gchar *host, int port, gboolean is_ssl,
2350 PurpleHttpSocketConnectCb cb, gpointer user_data)
2352 PurpleHttpKeepaliveRequest *req;
2353 PurpleHttpKeepaliveHost *kahost;
2354 gchar *hash;
2356 g_return_val_if_fail(pool != NULL, NULL);
2357 g_return_val_if_fail(host != NULL, NULL);
2359 if (pool->is_destroying) {
2360 purple_debug_error("http", "pool is destroying\n");
2361 return NULL;
2364 hash = purple_http_socket_hash(host, port, is_ssl);
2365 kahost = g_hash_table_lookup(pool->by_hash, hash);
2367 if (kahost == NULL) {
2368 kahost = g_new0(PurpleHttpKeepaliveHost, 1);
2369 kahost->pool = pool;
2370 kahost->host = g_strdup(host);
2371 kahost->port = port;
2372 kahost->is_ssl = is_ssl;
2374 g_hash_table_insert(pool->by_hash, g_strdup(hash), kahost);
2377 g_free(hash);
2379 req = g_new0(PurpleHttpKeepaliveRequest, 1);
2380 req->gc = gc;
2381 req->cb = cb;
2382 req->user_data = user_data;
2383 req->host = kahost;
2385 kahost->queue = g_slist_append(kahost->queue, req);
2387 purple_http_keepalive_host_process_queue(kahost);
2389 return req;
2392 static void
2393 _purple_http_keepalive_socket_connected(PurpleHttpSocket *hs,
2394 const gchar *error, gpointer _req)
2396 PurpleHttpKeepaliveRequest *req = _req;
2398 if (hs != NULL)
2399 hs->use_count++;
2401 req->cb(hs, error, req->user_data);
2402 g_free(req);
2405 static gboolean
2406 _purple_http_keepalive_host_process_queue_cb(gpointer _host)
2408 PurpleHttpKeepaliveRequest *req;
2409 PurpleHttpKeepaliveHost *host = _host;
2410 PurpleHttpSocket *hs = NULL;
2411 GSList *it;
2412 guint sockets_count;
2414 g_return_val_if_fail(host != NULL, FALSE);
2416 host->process_queue_timeout = 0;
2418 if (host->queue == NULL)
2419 return FALSE;
2421 sockets_count = 0;
2422 it = host->sockets;
2423 while (it != NULL) {
2424 PurpleHttpSocket *hs_current = it->data;
2426 sockets_count++;
2428 if (!hs_current->is_busy) {
2429 hs = hs_current;
2430 break;
2433 it = g_slist_next(it);
2436 /* There are no free sockets and we cannot create another one. */
2437 if (hs == NULL && sockets_count >= host->pool->limit_per_host &&
2438 host->pool->limit_per_host > 0)
2440 return FALSE;
2443 req = host->queue->data;
2444 host->queue = g_slist_remove(host->queue, req);
2446 if (hs != NULL) {
2447 if (purple_debug_is_verbose()) {
2448 purple_debug_misc("http", "locking a (previously used) "
2449 "socket: %p\n", hs);
2452 hs->is_busy = TRUE;
2453 hs->use_count++;
2455 purple_http_keepalive_host_process_queue(host);
2457 req->cb(hs, NULL, req->user_data);
2458 g_free(req);
2460 return FALSE;
2463 hs = purple_http_socket_connect_new(req->gc, req->host->host,
2464 req->host->port, req->host->is_ssl,
2465 _purple_http_keepalive_socket_connected, req);
2466 if (hs == NULL) {
2467 purple_debug_error("http", "failed creating new socket");
2468 return FALSE;
2471 req->hs = hs;
2472 hs->is_busy = TRUE;
2473 hs->host = host;
2475 if (purple_debug_is_verbose())
2476 purple_debug_misc("http", "locking a (new) socket: %p\n", hs);
2478 host->sockets = g_slist_append(host->sockets, hs);
2480 return FALSE;
2483 static void
2484 purple_http_keepalive_host_process_queue(PurpleHttpKeepaliveHost *host)
2486 g_return_if_fail(host != NULL);
2488 if (host->process_queue_timeout > 0)
2489 return;
2491 host->process_queue_timeout = g_timeout_add(0,
2492 _purple_http_keepalive_host_process_queue_cb, host);
2495 static void
2496 purple_http_keepalive_pool_request_cancel(PurpleHttpKeepaliveRequest *req)
2498 if (req == NULL)
2499 return;
2501 if (req->host != NULL)
2502 req->host->queue = g_slist_remove(req->host->queue, req);
2504 if (req->hs != NULL) {
2505 if (G_LIKELY(req->host)) {
2506 req->host->sockets = g_slist_remove(req->host->sockets,
2507 req->hs);
2509 purple_http_socket_close_free(req->hs);
2510 /* req should already be free'd here */
2511 } else {
2512 req->cb(NULL, _("Cancelled"), req->user_data);
2513 g_free(req);
2517 static void
2518 purple_http_keepalive_pool_release(PurpleHttpSocket *hs, gboolean invalidate)
2520 PurpleHttpKeepaliveHost *host;
2522 if (hs == NULL)
2523 return;
2525 if (purple_debug_is_verbose())
2526 purple_debug_misc("http", "releasing a socket: %p\n", hs);
2528 if (hs->input_source > 0) {
2529 g_source_remove(hs->input_source);
2530 hs->input_source = 0;
2533 if (hs->output_source > 0) {
2534 g_source_remove(hs->output_source);
2535 hs->output_source = 0;
2538 hs->is_busy = FALSE;
2539 host = hs->host;
2541 if (host == NULL) {
2542 purple_http_socket_close_free(hs);
2543 return;
2546 if (invalidate) {
2547 host->sockets = g_slist_remove(host->sockets, hs);
2548 purple_http_socket_close_free(hs);
2551 purple_http_keepalive_host_process_queue(host);
2554 void
2555 purple_http_keepalive_pool_set_limit_per_host(PurpleHttpKeepalivePool *pool,
2556 guint limit)
2558 g_return_if_fail(pool != NULL);
2560 pool->limit_per_host = limit;
2563 guint
2564 purple_http_keepalive_pool_get_limit_per_host(PurpleHttpKeepalivePool *pool)
2566 g_return_val_if_fail(pool != NULL, 0);
2568 return pool->limit_per_host;
2571 /*** HTTP connection set API **************************************************/
2573 PurpleHttpConnectionSet *
2574 purple_http_connection_set_new(void)
2576 PurpleHttpConnectionSet *set;
2578 set = g_new0(PurpleHttpConnectionSet, 1);
2579 set->connections = g_hash_table_new(g_direct_hash, g_direct_equal);
2581 return set;
2584 void
2585 purple_http_connection_set_destroy(PurpleHttpConnectionSet *set)
2587 if (set == NULL)
2588 return;
2590 set->is_destroying = TRUE;
2592 while (TRUE) {
2593 GHashTableIter iter;
2594 PurpleHttpConnection *http_conn;
2596 g_hash_table_iter_init(&iter, set->connections);
2597 if (!g_hash_table_iter_next(&iter, (gpointer*)&http_conn, NULL))
2598 break;
2600 purple_http_conn_cancel(http_conn);
2603 g_hash_table_destroy(set->connections);
2604 g_free(set);
2607 void
2608 purple_http_connection_set_add(PurpleHttpConnectionSet *set,
2609 PurpleHttpConnection *http_conn)
2611 if (set->is_destroying)
2612 return;
2613 if (http_conn->connection_set == set)
2614 return;
2615 if (http_conn->connection_set != NULL) {
2616 purple_http_connection_set_remove(http_conn->connection_set,
2617 http_conn);
2619 g_hash_table_insert(set->connections, http_conn, GINT_TO_POINTER(1));
2620 http_conn->connection_set = set;
2623 static void
2624 purple_http_connection_set_remove(PurpleHttpConnectionSet *set,
2625 PurpleHttpConnection *http_conn)
2627 g_hash_table_remove(set->connections, http_conn);
2628 if (http_conn->connection_set == set)
2629 http_conn->connection_set = NULL;
2632 /*** Request API **************************************************************/
2634 static void purple_http_request_free(PurpleHttpRequest *request);
2636 PurpleHttpRequest * purple_http_request_new(const gchar *url)
2638 PurpleHttpRequest *request;
2640 request = g_new0(PurpleHttpRequest, 1);
2642 request->ref_count = 1;
2643 request->url = g_strdup(url);
2644 request->headers = purple_http_headers_new();
2645 request->cookie_jar = purple_http_cookie_jar_new();
2646 request->keepalive_pool = purple_http_keepalive_pool_new();
2648 request->timeout = PURPLE_HTTP_REQUEST_DEFAULT_TIMEOUT;
2649 request->max_redirects = PURPLE_HTTP_REQUEST_DEFAULT_MAX_REDIRECTS;
2650 request->http11 = TRUE;
2651 request->max_length = PURPLE_HTTP_REQUEST_DEFAULT_MAX_LENGTH;
2653 return request;
2656 static void purple_http_request_free(PurpleHttpRequest *request)
2658 purple_http_headers_free(request->headers);
2659 purple_http_cookie_jar_unref(request->cookie_jar);
2660 purple_http_keepalive_pool_unref(request->keepalive_pool);
2661 g_free(request->method);
2662 g_free(request->contents);
2663 g_free(request->url);
2664 g_free(request);
2667 void purple_http_request_ref(PurpleHttpRequest *request)
2669 g_return_if_fail(request != NULL);
2671 request->ref_count++;
2674 PurpleHttpRequest * purple_http_request_unref(PurpleHttpRequest *request)
2676 if (request == NULL)
2677 return NULL;
2679 g_return_val_if_fail(request->ref_count > 0, NULL);
2681 request->ref_count--;
2682 if (request->ref_count > 0)
2683 return request;
2685 purple_http_request_free(request);
2686 return NULL;
2689 void purple_http_request_set_url(PurpleHttpRequest *request, const gchar *url)
2691 g_return_if_fail(request != NULL);
2692 g_return_if_fail(url != NULL);
2694 g_free(request->url);
2695 request->url = g_strdup(url);
2698 void purple_http_request_set_url_printf(PurpleHttpRequest *request,
2699 const gchar *format, ...)
2701 va_list args;
2702 gchar *value;
2704 g_return_if_fail(request != NULL);
2705 g_return_if_fail(format != NULL);
2707 va_start(args, format);
2708 value = g_strdup_vprintf(format, args);
2709 va_end(args);
2711 purple_http_request_set_url(request, value);
2712 g_free(value);
2715 const gchar * purple_http_request_get_url(PurpleHttpRequest *request)
2717 g_return_val_if_fail(request != NULL, NULL);
2719 return request->url;
2722 void purple_http_request_set_method(PurpleHttpRequest *request, const gchar *method)
2724 g_return_if_fail(request != NULL);
2726 g_free(request->method);
2727 request->method = g_strdup(method);
2730 const gchar * purple_http_request_get_method(PurpleHttpRequest *request)
2732 g_return_val_if_fail(request != NULL, NULL);
2734 return request->method;
2737 static gboolean purple_http_request_is_method(PurpleHttpRequest *request,
2738 const gchar *method)
2740 const gchar *rmethod;
2742 g_return_val_if_fail(request != NULL, FALSE);
2743 g_return_val_if_fail(method != NULL, FALSE);
2745 rmethod = purple_http_request_get_method(request);
2746 if (rmethod == NULL)
2747 return (g_ascii_strcasecmp(method, "get") == 0);
2748 return (g_ascii_strcasecmp(method, rmethod) == 0);
2751 void
2752 purple_http_request_set_keepalive_pool(PurpleHttpRequest *request,
2753 PurpleHttpKeepalivePool *pool)
2755 g_return_if_fail(request != NULL);
2757 if (pool != NULL)
2758 purple_http_keepalive_pool_ref(pool);
2760 if (request->keepalive_pool != NULL) {
2761 purple_http_keepalive_pool_unref(request->keepalive_pool);
2762 request->keepalive_pool = NULL;
2765 if (pool != NULL)
2766 request->keepalive_pool = pool;
2769 PurpleHttpKeepalivePool *
2770 purple_http_request_get_keepalive_pool(PurpleHttpRequest *request)
2772 g_return_val_if_fail(request != NULL, FALSE);
2774 return request->keepalive_pool;
2777 void purple_http_request_set_contents(PurpleHttpRequest *request,
2778 const gchar *contents, int length)
2780 g_return_if_fail(request != NULL);
2781 g_return_if_fail(length >= -1);
2783 request->contents_reader = NULL;
2784 request->contents_reader_data = NULL;
2786 g_free(request->contents);
2787 if (contents == NULL || length == 0) {
2788 request->contents = NULL;
2789 request->contents_length = 0;
2790 return;
2793 if (length == -1)
2794 length = strlen(contents);
2795 request->contents = g_memdup(contents, length);
2796 request->contents_length = length;
2799 void purple_http_request_set_contents_reader(PurpleHttpRequest *request,
2800 PurpleHttpContentReader reader, int contents_length, gpointer user_data)
2802 g_return_if_fail(request != NULL);
2803 g_return_if_fail(reader != NULL);
2804 g_return_if_fail(contents_length >= -1);
2806 g_free(request->contents);
2807 request->contents = NULL;
2808 request->contents_length = contents_length;
2809 request->contents_reader = reader;
2810 request->contents_reader_data = user_data;
2813 void purple_http_request_set_response_writer(PurpleHttpRequest *request,
2814 PurpleHttpContentWriter writer, gpointer user_data)
2816 g_return_if_fail(request != NULL);
2818 if (writer == NULL)
2819 user_data = NULL;
2820 request->response_writer = writer;
2821 request->response_writer_data = user_data;
2824 void purple_http_request_set_timeout(PurpleHttpRequest *request, int timeout)
2826 g_return_if_fail(request != NULL);
2828 if (timeout < -1)
2829 timeout = -1;
2831 request->timeout = timeout;
2834 int purple_http_request_get_timeout(PurpleHttpRequest *request)
2836 g_return_val_if_fail(request != NULL, -1);
2838 return request->timeout;
2841 void purple_http_request_set_max_redirects(PurpleHttpRequest *request,
2842 int max_redirects)
2844 g_return_if_fail(request != NULL);
2846 if (max_redirects < -1)
2847 max_redirects = -1;
2849 request->max_redirects = max_redirects;
2852 int purple_http_request_get_max_redirects(PurpleHttpRequest *request)
2854 g_return_val_if_fail(request != NULL, -1);
2856 return request->max_redirects;
2859 void purple_http_request_set_cookie_jar(PurpleHttpRequest *request,
2860 PurpleHttpCookieJar *cookie_jar)
2862 g_return_if_fail(request != NULL);
2863 g_return_if_fail(cookie_jar != NULL);
2865 purple_http_cookie_jar_ref(cookie_jar);
2866 purple_http_cookie_jar_unref(request->cookie_jar);
2867 request->cookie_jar = cookie_jar;
2870 PurpleHttpCookieJar * purple_http_request_get_cookie_jar(
2871 PurpleHttpRequest *request)
2873 g_return_val_if_fail(request != NULL, NULL);
2875 return request->cookie_jar;
2878 void purple_http_request_set_http11(PurpleHttpRequest *request, gboolean http11)
2880 g_return_if_fail(request != NULL);
2882 request->http11 = http11;
2885 gboolean purple_http_request_is_http11(PurpleHttpRequest *request)
2887 g_return_val_if_fail(request != NULL, FALSE);
2889 return request->http11;
2892 void purple_http_request_set_max_len(PurpleHttpRequest *request, int max_len)
2894 g_return_if_fail(request != NULL);
2896 if (max_len < 0 || max_len > PURPLE_HTTP_REQUEST_HARD_MAX_LENGTH)
2897 max_len = PURPLE_HTTP_REQUEST_HARD_MAX_LENGTH;
2899 request->max_length = max_len;
2902 int purple_http_request_get_max_len(PurpleHttpRequest *request)
2904 g_return_val_if_fail(request != NULL, -1);
2906 return request->max_length;
2909 void purple_http_request_header_set(PurpleHttpRequest *request,
2910 const gchar *key, const gchar *value)
2912 g_return_if_fail(request != NULL);
2913 g_return_if_fail(key != NULL);
2915 purple_http_headers_remove(request->headers, key);
2916 if (value)
2917 purple_http_headers_add(request->headers, key, value);
2920 void purple_http_request_header_set_printf(PurpleHttpRequest *request,
2921 const gchar *key, const gchar *format, ...)
2923 va_list args;
2924 gchar *value;
2926 g_return_if_fail(request != NULL);
2927 g_return_if_fail(key != NULL);
2928 g_return_if_fail(format != NULL);
2930 va_start(args, format);
2931 value = g_strdup_vprintf(format, args);
2932 va_end(args);
2934 purple_http_request_header_set(request, key, value);
2935 g_free(value);
2938 void purple_http_request_header_add(PurpleHttpRequest *request,
2939 const gchar *key, const gchar *value)
2941 g_return_if_fail(request != NULL);
2942 g_return_if_fail(key != NULL);
2944 purple_http_headers_add(request->headers, key, value);
2947 /*** HTTP response API ********************************************************/
2949 static PurpleHttpResponse * purple_http_response_new(void)
2951 PurpleHttpResponse *response = g_new0(PurpleHttpResponse, 1);
2953 return response;
2956 static void purple_http_response_free(PurpleHttpResponse *response)
2958 if (response->contents != NULL)
2959 g_string_free(response->contents, TRUE);
2960 g_free(response->error);
2961 purple_http_headers_free(response->headers);
2962 g_free(response);
2965 gboolean purple_http_response_is_successful(PurpleHttpResponse *response)
2967 int code;
2969 g_return_val_if_fail(response != NULL, FALSE);
2971 code = response->code;
2973 if (code <= 0)
2974 return FALSE;
2976 /* TODO: HTTP/1.1 100 Continue */
2978 if (code / 100 == 2)
2979 return TRUE;
2981 return FALSE;
2984 int purple_http_response_get_code(PurpleHttpResponse *response)
2986 g_return_val_if_fail(response != NULL, 0);
2988 return response->code;
2991 const gchar * purple_http_response_get_error(PurpleHttpResponse *response)
2993 g_return_val_if_fail(response != NULL, NULL);
2995 if (response->error != NULL)
2996 return response->error;
2998 if (!purple_http_response_is_successful(response)) {
2999 static gchar errmsg[200];
3000 if (response->code <= 0) {
3001 g_snprintf(errmsg, sizeof(errmsg),
3002 _("Unknown HTTP error"));
3003 } else {
3004 g_snprintf(errmsg, sizeof(errmsg),
3005 _("Invalid HTTP response code (%d)"),
3006 response->code);
3008 return errmsg;
3011 return NULL;
3014 gsize purple_http_response_get_data_len(PurpleHttpResponse *response)
3016 g_return_val_if_fail(response != NULL, 0);
3018 if (response->contents == NULL)
3019 return 0;
3021 return response->contents->len;
3024 const gchar * purple_http_response_get_data(PurpleHttpResponse *response, size_t *len)
3026 const gchar *ret = "";
3028 g_return_val_if_fail(response != NULL, "");
3030 if (response->contents != NULL) {
3031 ret = response->contents->str;
3032 if (len)
3033 *len = response->contents->len;
3034 } else {
3035 if (len)
3036 *len = 0;
3039 return ret;
3042 const GList * purple_http_response_get_all_headers(PurpleHttpResponse *response)
3044 g_return_val_if_fail(response != NULL, NULL);
3046 return purple_http_headers_get_all(response->headers);
3049 const GList * purple_http_response_get_headers_by_name(
3050 PurpleHttpResponse *response, const gchar *name)
3052 g_return_val_if_fail(response != NULL, NULL);
3053 g_return_val_if_fail(name != NULL, NULL);
3055 return purple_http_headers_get_all_by_name(response->headers, name);
3058 const gchar * purple_http_response_get_header(PurpleHttpResponse *response,
3059 const gchar *name)
3061 g_return_val_if_fail(response != NULL, NULL);
3062 g_return_val_if_fail(name != NULL, NULL);
3064 return purple_http_headers_get(response->headers, name);
3067 /*** URL functions ************************************************************/
3069 PurpleHttpURL *
3070 purple_http_url_parse(const char *raw_url)
3072 PurpleHttpURL *url;
3073 GMatchInfo *match_info;
3075 gchar *host_full, *tmp;
3077 g_return_val_if_fail(raw_url != NULL, NULL);
3079 if (!g_regex_match(purple_http_re_url, raw_url, 0, &match_info)) {
3080 if (purple_debug_is_verbose() && purple_debug_is_unsafe()) {
3081 purple_debug_warning("http",
3082 "Invalid URL provided: %s\n",
3083 raw_url);
3085 return NULL;
3088 url = g_new0(PurpleHttpURL, 1);
3090 url->protocol = g_match_info_fetch(match_info, 1);
3091 host_full = g_match_info_fetch(match_info, 2);
3092 url->path = g_match_info_fetch(match_info, 3);
3093 url->fragment = g_match_info_fetch(match_info, 4);
3094 g_match_info_free(match_info);
3096 if (g_strcmp0(url->protocol, "") == 0) {
3097 g_free(url->protocol);
3098 url->protocol = NULL;
3099 } else if (url->protocol != NULL) {
3100 tmp = url->protocol;
3101 url->protocol = g_ascii_strdown(url->protocol, -1);
3102 g_free(tmp);
3104 if (host_full[0] == '\0') {
3105 g_free(host_full);
3106 host_full = NULL;
3108 if (url->path[0] == '\0') {
3109 g_free(url->path);
3110 url->path = NULL;
3112 if ((url->protocol == NULL) != (host_full == NULL))
3113 purple_debug_warning("http", "Protocol or host not present "
3114 "(unlikely case)\n");
3116 if (host_full) {
3117 gchar *port_str;
3119 if (!g_regex_match(purple_http_re_url_host, host_full, 0,
3120 &match_info))
3122 if (purple_debug_is_verbose() &&
3123 purple_debug_is_unsafe())
3125 purple_debug_warning("http",
3126 "Invalid host provided for URL: %s\n",
3127 raw_url);
3130 g_free(host_full);
3131 purple_http_url_free(url);
3132 return NULL;
3135 url->username = g_match_info_fetch(match_info, 1);
3136 url->password = g_match_info_fetch(match_info, 2);
3137 url->host = g_match_info_fetch(match_info, 3);
3138 port_str = g_match_info_fetch(match_info, 4);
3140 if (port_str && port_str[0])
3141 url->port = atoi(port_str);
3143 if (url->username[0] == '\0') {
3144 g_free(url->username);
3145 url->username = NULL;
3147 if (url->password[0] == '\0') {
3148 g_free(url->password);
3149 url->password = NULL;
3151 if (g_strcmp0(url->host, "") == 0) {
3152 g_free(url->host);
3153 url->host = NULL;
3154 } else if (url->host != NULL) {
3155 tmp = url->host;
3156 url->host = g_ascii_strdown(url->host, -1);
3157 g_free(tmp);
3160 g_free(port_str);
3161 g_match_info_free(match_info);
3163 g_free(host_full);
3164 host_full = NULL;
3167 if (url->host != NULL) {
3168 if (url->protocol == NULL)
3169 url->protocol = g_strdup("http");
3170 if (url->port == 0 && 0 == strcmp(url->protocol, "http"))
3171 url->port = 80;
3172 if (url->port == 0 && 0 == strcmp(url->protocol, "https"))
3173 url->port = 443;
3174 if (url->path == NULL)
3175 url->path = g_strdup("/");
3176 if (url->path[0] != '/')
3177 purple_debug_warning("http",
3178 "URL path doesn't start with slash\n");
3181 return url;
3184 void
3185 purple_http_url_free(PurpleHttpURL *parsed_url)
3187 if (parsed_url == NULL)
3188 return;
3190 g_free(parsed_url->protocol);
3191 g_free(parsed_url->username);
3192 g_free(parsed_url->password);
3193 g_free(parsed_url->host);
3194 g_free(parsed_url->path);
3195 g_free(parsed_url->fragment);
3196 g_free(parsed_url);
3199 void
3200 purple_http_url_relative(PurpleHttpURL *base_url, PurpleHttpURL *relative_url)
3202 g_return_if_fail(base_url != NULL);
3203 g_return_if_fail(relative_url != NULL);
3205 if (relative_url->host) {
3206 g_free(base_url->protocol);
3207 base_url->protocol = g_strdup(relative_url->protocol);
3208 g_free(base_url->username);
3209 base_url->username = g_strdup(relative_url->username);
3210 g_free(base_url->password);
3211 base_url->password = g_strdup(relative_url->password);
3212 g_free(base_url->host);
3213 base_url->host = g_strdup(relative_url->host);
3214 base_url->port = relative_url->port;
3216 g_free(base_url->path);
3217 base_url->path = NULL;
3220 if (relative_url->path) {
3221 if (relative_url->path[0] == '/' ||
3222 base_url->path == NULL)
3224 g_free(base_url->path);
3225 base_url->path = g_strdup(relative_url->path);
3226 } else {
3227 gchar *last_slash = strrchr(base_url->path, '/');
3228 gchar *tmp;
3229 if (last_slash == NULL)
3230 base_url->path[0] = '\0';
3231 else
3232 last_slash[1] = '\0';
3233 tmp = base_url->path;
3234 base_url->path = g_strconcat(base_url->path,
3235 relative_url->path, NULL);
3236 g_free(tmp);
3240 g_free(base_url->fragment);
3241 base_url->fragment = g_strdup(relative_url->fragment);
3244 gchar *
3245 purple_http_url_print(PurpleHttpURL *parsed_url)
3247 GString *url = g_string_new("");
3248 gboolean before_host_printed = FALSE, host_printed = FALSE;
3249 gboolean port_is_default = FALSE;
3251 if (parsed_url->protocol) {
3252 g_string_append_printf(url, "%s://", parsed_url->protocol);
3253 before_host_printed = TRUE;
3254 if (parsed_url->port == 80 && 0 == strcmp(parsed_url->protocol,
3255 "http"))
3256 port_is_default = TRUE;
3257 if (parsed_url->port == 443 && 0 == strcmp(parsed_url->protocol,
3258 "https"))
3259 port_is_default = TRUE;
3261 if (parsed_url->username || parsed_url->password) {
3262 if (parsed_url->username)
3263 g_string_append(url, parsed_url->username);
3264 g_string_append_c(url, ':');
3265 if (parsed_url->password)
3266 g_string_append(url, parsed_url->password);
3267 g_string_append(url, "@");
3268 before_host_printed = TRUE;
3270 if (parsed_url->host || parsed_url->port) {
3271 if (!parsed_url->host)
3272 g_string_append_printf(url, "{???}:%d",
3273 parsed_url->port);
3274 else {
3275 g_string_append(url, parsed_url->host);
3276 if (!port_is_default)
3277 g_string_append_printf(url, ":%d",
3278 parsed_url->port);
3280 host_printed = TRUE;
3282 if (parsed_url->path) {
3283 if (!host_printed && before_host_printed)
3284 g_string_append(url, "{???}");
3285 g_string_append(url, parsed_url->path);
3287 if (parsed_url->fragment)
3288 g_string_append_printf(url, "#%s", parsed_url->fragment);
3290 return g_string_free(url, FALSE);
3293 const gchar *
3294 purple_http_url_get_protocol(const PurpleHttpURL *parsed_url)
3296 g_return_val_if_fail(parsed_url != NULL, NULL);
3298 return parsed_url->protocol;
3301 const gchar *
3302 purple_http_url_get_username(const PurpleHttpURL *parsed_url)
3304 g_return_val_if_fail(parsed_url != NULL, NULL);
3306 return parsed_url->username;
3309 const gchar *
3310 purple_http_url_get_password(const PurpleHttpURL *parsed_url)
3312 g_return_val_if_fail(parsed_url != NULL, NULL);
3314 return parsed_url->password;
3317 const gchar *
3318 purple_http_url_get_host(const PurpleHttpURL *parsed_url)
3320 g_return_val_if_fail(parsed_url != NULL, NULL);
3322 return parsed_url->host;
3326 purple_http_url_get_port(const PurpleHttpURL *parsed_url)
3328 g_return_val_if_fail(parsed_url != NULL, 0);
3330 return parsed_url->port;
3333 const gchar *
3334 purple_http_url_get_path(const PurpleHttpURL *parsed_url)
3336 g_return_val_if_fail(parsed_url != NULL, NULL);
3338 return parsed_url->path;
3341 const gchar *
3342 purple_http_url_get_fragment(const PurpleHttpURL *parsed_url)
3344 g_return_val_if_fail(parsed_url != NULL, NULL);
3346 return parsed_url->fragment;
3349 /*** HTTP Subsystem ***********************************************************/
3351 void purple_http_init(void)
3353 purple_http_re_url = g_regex_new("^"
3355 "(?:" /* host part beginning */
3356 "([a-z]+)\\:/*" /* protocol */
3357 "([^/]+)" /* username, password, host, port */
3358 ")?" /* host part ending */
3360 "([^#]*)" /* path */
3362 "(?:#" "(.*)" ")?" /* fragment */
3364 "$", G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
3365 G_REGEX_MATCH_NOTEMPTY, NULL);
3367 purple_http_re_url_host = g_regex_new("^"
3369 "(?:" /* user credentials part beginning */
3370 "([" PURPLE_HTTP_URL_CREDENTIALS_CHARS "]+)" /* username */
3371 "(?::([" PURPLE_HTTP_URL_CREDENTIALS_CHARS "]+))" /* password */
3372 "@)?" /* user credentials part ending */
3374 "([a-z0-9.-]+)" /* host */
3375 "(?::([0-9]+))?" /* port*/
3377 "$", G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
3378 G_REGEX_MATCH_NOTEMPTY, NULL);
3380 purple_http_re_rfc1123 = g_regex_new(
3381 "^[a-z]+, " /* weekday */
3382 "([0-9]+) " /* date */
3383 "([a-z]+) " /* month */
3384 "([0-9]+) " /* year */
3385 "([0-9]+:[0-9]+:[0-9]+) " /* time */
3386 "(?:GMT|UTC)$",
3387 G_REGEX_OPTIMIZE | G_REGEX_CASELESS,
3388 G_REGEX_MATCH_NOTEMPTY, NULL);
3390 purple_http_hc_list = NULL;
3391 purple_http_hc_by_ptr = g_hash_table_new(g_direct_hash, g_direct_equal);
3392 purple_http_hc_by_gc = g_hash_table_new_full(g_direct_hash,
3393 g_direct_equal, NULL, (GDestroyNotify)g_list_free);
3394 purple_http_cancelling_gc = g_hash_table_new(g_direct_hash, g_direct_equal);
3397 static void purple_http_foreach_conn_cancel(gpointer _hc, gpointer user_data)
3399 PurpleHttpConnection *hc = _hc;
3400 purple_http_conn_cancel(hc);
3403 void purple_http_uninit(void)
3405 g_regex_unref(purple_http_re_url);
3406 purple_http_re_url = NULL;
3407 g_regex_unref(purple_http_re_url_host);
3408 purple_http_re_url_host = NULL;
3409 g_regex_unref(purple_http_re_rfc1123);
3410 purple_http_re_rfc1123 = NULL;
3412 if (purple_http_hc_list != NULL) {
3413 g_list_foreach(purple_http_hc_list, purple_http_foreach_conn_cancel, NULL);
3416 if (purple_http_hc_list != NULL ||
3417 0 != g_hash_table_size(purple_http_hc_by_ptr) ||
3418 0 != g_hash_table_size(purple_http_hc_by_gc))
3419 purple_debug_warning("http",
3420 "Couldn't cleanup all connections.\n");
3422 g_list_free(purple_http_hc_list);
3423 purple_http_hc_list = NULL;
3424 g_hash_table_destroy(purple_http_hc_by_gc);
3425 purple_http_hc_by_gc = NULL;
3426 g_hash_table_destroy(purple_http_hc_by_ptr);
3427 purple_http_hc_by_ptr = NULL;
3428 g_hash_table_destroy(purple_http_cancelling_gc);
3429 purple_http_cancelling_gc = NULL;