changeset 14354:01daacf7b771

[gaim-migrate @ 17060] Make gaim_url_fetch() cancelable and change Yahoo! to take advantage of the changes. Other stuff can be changed later, the important thing is that the API is there. committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Sun, 27 Aug 2006 21:13:30 +0000
parents 33dc9f22b528
children fb675c9eef7a
files doc/ChangeLog.API gtk/gtkprefs.c gtk/plugins/relnot.c libgaim/protocols/msn/msn.c libgaim/protocols/oscar/oscar.c libgaim/protocols/yahoo/yahoo.c libgaim/protocols/yahoo/yahoo.h libgaim/protocols/yahoo/yahoo_picture.c libgaim/protocols/yahoo/yahoo_profile.c libgaim/upnp.c libgaim/util.c libgaim/util.h
diffstat 12 files changed, 285 insertions(+), 168 deletions(-) [+]
line wrap: on
line diff
--- a/doc/ChangeLog.API	Sun Aug 27 19:47:41 2006 +0000
+++ b/doc/ChangeLog.API	Sun Aug 27 21:13:30 2006 +0000
@@ -122,6 +122,9 @@
 	* gaim_gethostbyname_async(): Renamed to gaim_dnsquery_a() and
 	  changed to return a pointer to a data structure that can be
 	  used to cancel the pending DNS query using gaim_dnsquery_destroy()
+	* gaim_url_fetch(): Renamed to gaim_util_fetch_url() and changed
+	  to return a pointer to a data structer that can be used to cancel
+	  the pending HTTP request using gaim_util_fetch_url_cancel().
 	* gaim_gtk_create_imhtml(): Added sw_ret() parameter
 	* gaim_account_get_log(): Added create parameter
 	* GAIM_CMD_P_VERYHIGH is now GAIM_CMD_P_VERY_HIGH
@@ -283,7 +286,7 @@
 	* gaim_gtk_log_init()
 	* gaim_gtk_log_get_handle()
 	* gaim_gtk_log_uninit()
-	* gaim_url_fetch_request()
+	* gaim_util_fetch_url_request()
 	* GaimMenuAction
 	* gaim_menu_action_new()
 	* gaim_menu_action_free()
@@ -310,6 +313,8 @@
 	  gaim_proxy_connect() request
 	* gaim_dnsquery_destroy(): Can be used to cancel a pending DNS
 	  query.
+	* gaim_util_fetch_url_cancel(): Can be used to cancel a pending
+	  call to gaim_util_fetch_url() or gaim_util_fetch_url_request().
 
 	Signals - Changed:  (See the Doxygen docs for details on all signals.)
 	* Signal propagation now stops after a handler returns a non-NULL value.
--- a/gtk/gtkprefs.c	Sun Aug 27 19:47:41 2006 +0000
+++ b/gtk/gtkprefs.c	Sun Aug 27 21:13:30 2006 +0000
@@ -526,26 +526,29 @@
 }
 
 static void
-theme_got_url(void *data, const char *themedata, size_t len)
+theme_got_url(GaimUtilFetchUrlData *url_data, gpointer user_data,
+		const gchar *themedata, size_t len, const gchar *error_message)
 {
 	FILE *f;
 	gchar *path;
 
-	if (len == 0)
+	if ((error_message != NULL) || (len == 0))
 		return;
 
 	f = gaim_mkstemp(&path, TRUE);
 	fwrite(themedata, len, 1, f);
 	fclose(f);
 
-	theme_install_theme(path, data);
+	theme_install_theme(path, user_data);
 
 	g_unlink(path);
 	g_free(path);
 }
 
-static void theme_dnd_recv(GtkWidget *widget, GdkDragContext *dc, guint x, guint y, GtkSelectionData *sd,
-				guint info, guint t, gpointer data) {
+static void
+theme_dnd_recv(GtkWidget *widget, GdkDragContext *dc, guint x, guint y,
+		GtkSelectionData *sd, guint info, guint t, gpointer data)
+{
 	gchar *name = (gchar *)sd->data;
 
 	if ((sd->length >= 0) && (sd->format == 8)) {
@@ -568,9 +571,9 @@
 		} else if (!g_ascii_strncasecmp(name, "http://", 7)) {
 			/* Oo, a web drag and drop. This is where things
 			 * will start to get interesting */
-			gaim_url_fetch(name, TRUE, NULL, FALSE, theme_got_url, ".tgz");
+			gaim_util_fetch_url(name, TRUE, NULL, FALSE, theme_got_url, ".tgz");
 		} else if (!g_ascii_strncasecmp(name, "https://", 8)) {
-			/* gaim_url_fetch() doesn't support HTTPS, but we want users
+			/* gaim_util_fetch_url() doesn't support HTTPS, but we want users
 			 * to be able to drag and drop links from the SF trackers, so
 			 * we'll try it as an HTTP URL. */
 			char *tmp = g_strdup(name + 1);
@@ -578,7 +581,7 @@
 			tmp[1] = 't';
 			tmp[2] = 't';
 			tmp[3] = 'p';
-			gaim_url_fetch(tmp, TRUE, NULL, FALSE, theme_got_url, ".tgz");
+			gaim_util_fetch_url(tmp, TRUE, NULL, FALSE, theme_got_url, ".tgz");
 			g_free(tmp);
 		}
 
--- a/gtk/plugins/relnot.c	Sun Aug 27 19:47:41 2006 +0000
+++ b/gtk/plugins/relnot.c	Sun Aug 27 21:13:30 2006 +0000
@@ -42,14 +42,14 @@
 #define MIN_CHECK_INTERVAL 60 * 60 * 24
 
 static void
-version_fetch_cb(void *ud, const char *data, size_t len)
+version_fetch_cb(GaimUtilFetchUrlData *url_data, gpointer user_data,
+		const gchar *changelog, size_t len, const gchar *error_message)
 {
-	const char *changelog = data;
 	char *cur_ver, *formatted;
 	GString *message;
 	int i=0;
 
-	if(!changelog || !len)
+	if(error_message || !changelog || !len)
 		return;
 
 	while(changelog[i] && changelog[i] != '\n') i++;
@@ -101,7 +101,7 @@
 				"gaim"
 #endif
 		);
-		gaim_url_fetch(url, TRUE, NULL, FALSE, version_fetch_cb, NULL);
+		gaim_util_fetch_url(url, TRUE, NULL, FALSE, version_fetch_cb, NULL);
 		gaim_prefs_set_int("/plugins/gtk/relnot/last_check", time(NULL));
 		g_free(url);
 	}
--- a/libgaim/protocols/msn/msn.c	Sun Aug 27 19:47:41 2006 +0000
+++ b/libgaim/protocols/msn/msn.c	Sun Aug 27 21:13:30 2006 +0000
@@ -1402,7 +1402,8 @@
 	return NULL;
 }
 
-static void msn_got_photo(void *data, const char *url_text, size_t len);
+static void msn_got_photo(GaimUtilFetchUrlData *url_data, gpointer data,
+		const gchar *url_text, size_t len, const gchar *error_message);
 
 #endif
 
@@ -1424,7 +1425,8 @@
 		sect_info = TRUE;
 
 static void
-msn_got_info(void *data, const char *url_text, size_t len)
+msn_got_info(GaimUtilFetchUrlData *url_data, gpointer data,
+		const gchar *url_text, size_t len, const gchar *error_message)
 {
 	MsnGetInfoData *info_data = (MsnGetInfoData *)data;
 	char *stripped, *p, *q;
@@ -1459,7 +1461,7 @@
 	tooltip_text = msn_tooltip_info_text(info_data);
 	title = _("MSN Profile");
 
-	if (url_text == NULL || strcmp(url_text, "") == 0)
+	if (error_message != NULL || url_text == NULL || strcmp(url_text, "") == 0)
 	{
 		g_snprintf(buf, 1024, "<html><body>%s<b>%s</b></body></html>",
 				tooltip_text, _("Error retrieving profile"));
@@ -1824,20 +1826,22 @@
 	/* Try to put the photo in there too, if there's one */
 	if (photo_url_text)
 	{
-		gaim_url_fetch(photo_url_text, FALSE, NULL, FALSE, msn_got_photo,
+		gaim_util_fetch_url(photo_url_text, FALSE, NULL, FALSE, msn_got_photo,
 					   info2_data);
 	}
 	else
 	{
 		/* Emulate a callback */
-		msn_got_photo(info2_data, NULL, 0);
+		/* TODO: Huh? */
+		msn_got_photo(NULL, info2_data, NULL, 0, NULL);
 	}
 }
 
 static void
-msn_got_photo(void *data, const char *url_text, size_t len)
+msn_got_photo(GaimUtilFetchUrlData *url_data, gpointer user_data,
+		const gchar *url_text, size_t len, const gchar *error_message)
 {
-	MsnGetInfoStepTwoData *info2_data = (MsnGetInfoStepTwoData *)data;
+	MsnGetInfoStepTwoData *info2_data = (MsnGetInfoStepTwoData *)user_data;
 	int id = -1;
 
 	/* Unmarshall the saved state */
@@ -1849,7 +1853,7 @@
 	char *tooltip_text = info2_data->tooltip_text;
 
 	/* Make sure the connection is still valid if we got here by fetching a photo url */
-	if (url_text &&
+	if (error_message == NULL || url_text != NULL ||
 		g_list_find(gaim_connections_get_all(), info_data->gc) == NULL)
 	{
 		gaim_debug_warning("msn", "invalid connection. ignoring buddy photo info.\n");
@@ -1866,7 +1870,7 @@
 	}
 
 	/* Try to put the photo in there too, if there's one and is readable */
-	if (data && url_text && len != 0)
+	if (user_data && url_text && len != 0)
 	{
 		if (strstr(url_text, "400 Bad Request")
 			|| strstr(url_text, "403 Forbidden")
@@ -1918,7 +1922,7 @@
 
 	url = g_strdup_printf("%s%s", PROFILE_URL, name);
 
-	gaim_url_fetch(url, FALSE,
+	gaim_util_fetch_url(url, FALSE,
 				   "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)",
 				   TRUE, msn_got_info, data);
 
--- a/libgaim/protocols/oscar/oscar.c	Sun Aug 27 19:47:41 2006 +0000
+++ b/libgaim/protocols/oscar/oscar.c	Sun Aug 27 21:13:30 2006 +0000
@@ -1372,7 +1372,7 @@
 	return 1;
 }
 
-/* XXX - Should use gaim_url_fetch for the below stuff */
+/* XXX - Should use gaim_util_fetch_url for the below stuff */
 struct pieceofcrap {
 	GaimConnection *gc;
 	unsigned long offset;
--- a/libgaim/protocols/yahoo/yahoo.c	Sun Aug 27 19:47:41 2006 +0000
+++ b/libgaim/protocols/yahoo/yahoo.c	Sun Aug 27 21:13:30 2006 +0000
@@ -52,7 +52,7 @@
 /* #define YAHOO_DEBUG */
 
 static void yahoo_add_buddy(GaimConnection *gc, GaimBuddy *, GaimGroup *);
-static void yahoo_login_page_cb(void *user_data, const char *buf, size_t len);
+static void yahoo_login_page_cb(GaimUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message);
 static void yahoo_set_status(GaimAccount *account, GaimStatus *status);
 
 static void
@@ -1885,13 +1885,16 @@
 		break;
 	case 13:
 		if (!yd->wm) {
+			GaimUtilFetchUrlData *url_data;
 			yd->wm = TRUE;
 			if (yd->fd >= 0)
 				close(yd->fd);
 			if (gc->inpa)
 				gaim_input_remove(gc->inpa);
-			gaim_url_fetch(WEBMESSENGER_URL, TRUE, "Gaim/" VERSION, FALSE,
-			               yahoo_login_page_cb, gc);
+			url_data = gaim_util_fetch_url(WEBMESSENGER_URL, TRUE,
+					"Gaim/" VERSION, FALSE, yahoo_login_page_cb, gc);
+			if (url_data != NULL)
+				yd->url_datas = g_slist_prepend(yd->url_datas, url_data);
 			gaim_notify_warning(gc, NULL, _("Normal authentication failed!"),
 			                    _("The normal authentication method has failed. "
 			                      "This means either your password is incorrect, "
@@ -2523,14 +2526,16 @@
 	return hash;
 }
 
-static void yahoo_login_page_cb(void *user_data, const char *buf, size_t len)
+static void
+yahoo_login_page_cb(GaimUtilFetchUrlData *url_data, gpointer user_data,
+		const gchar *url_text, size_t len, const gchar *error_message)
 {
 	GaimConnection *gc = (GaimConnection *)user_data;
 	GaimAccount *account = gaim_connection_get_account(gc);
 	struct yahoo_data *yd = gc->proto_data;
 	const char *sn = gaim_account_get_username(account);
 	const char *pass = gaim_connection_get_password(gc);
-	GHashTable *hash = yahoo_login_page_hash(buf, len);
+	GHashTable *hash = yahoo_login_page_hash(url_text, len);
 	GString *url = g_string_new("GET http://login.yahoo.com/config/login?login=");
 	char md5[33], *hashp = md5, *chal;
 	int i;
@@ -2538,6 +2543,8 @@
 	GaimCipherContext *context;
 	guchar digest[16];
 
+	yd->url_datas = g_slist_remove(yd->url_datas, url_data);
+
 	url = g_string_append(url, sn);
 	url = g_string_append(url, "&passwd=");
 
@@ -2709,6 +2716,11 @@
 	if (gc->inpa)
 		gaim_input_remove(gc->inpa);
 
+	while (yd->url_datas) {
+		gaim_util_fetch_url_cancel(yd->url_datas->data);
+		yd->url_datas = g_slist_delete_link(yd->url_datas, yd->url_datas);
+	}
+
 	for (l = yd->confs; l; l = l->next) {
 		GaimConversation *conv = l->data;
 
--- a/libgaim/protocols/yahoo/yahoo.h	Sun Aug 27 19:47:41 2006 +0000
+++ b/libgaim/protocols/yahoo/yahoo.h	Sun Aug 27 21:13:30 2006 +0000
@@ -139,6 +139,12 @@
 	struct yahoo_buddy_icon_upload_data *picture_upload_todo;
 
 	struct _YchtConn *ycht;
+
+	/**
+	 * This linked list contains GaimUtilFetchUrlData structs
+	 * for when we lookup people profile or photo information.
+	 */
+	GSList *url_datas;
 };
 
 #define YAHOO_MAX_STATUS_MESSAGE_LENGTH (255)
--- a/libgaim/protocols/yahoo/yahoo_picture.c	Sun Aug 27 19:47:41 2006 +0000
+++ b/libgaim/protocols/yahoo/yahoo_picture.c	Sun Aug 27 21:13:30 2006 +0000
@@ -43,18 +43,27 @@
 	int checksum;
 };
 
-static void yahoo_fetch_picture_cb(void *user_data, const char *pic_data, size_t len)
+static void
+yahoo_fetch_picture_cb(GaimUtilFetchUrlData *url_data, gpointer user_data,
+		const gchar *pic_data, size_t len, const gchar *error_message)
 {
-	struct yahoo_fetch_picture_data *d = user_data;
+	struct yahoo_fetch_picture_data *d;
+	struct yahoo_data *yd;
 	GaimBuddy *b;
 
-	if (GAIM_CONNECTION_IS_VALID(d->gc) && len) {
+	d = user_data;
+	yd = d->gc->proto_data;
+	yd->url_datas = g_slist_remove(yd->url_datas, url_data);
+
+	if (error_message != NULL) {
+		gaim_debug_error("yahoo", "Fetching buddy icon failed: %s\n", error_message);
+	} else if (len == 0) {
+		gaim_debug_error("yahoo", "Fetched an icon with length 0.  Strange.\n");
+	} else {
 		gaim_buddy_icons_set_for_user(gaim_connection_get_account(d->gc), d->who, (void *)pic_data, len);
 		b = gaim_find_buddy(gaim_connection_get_account(d->gc), d->who);
 		if (b)
 			gaim_blist_node_set_int((GaimBlistNode*)b, YAHOO_ICON_CHECKSUM_KEY, d->checksum);
-	} else {
-		gaim_debug_error("yahoo", "Fetching buddy icon failed.\n");
 	}
 
 	g_free(d->who);
@@ -63,6 +72,7 @@
 
 void yahoo_process_picture(GaimConnection *gc, struct yahoo_packet *pkt)
 {
+	struct yahoo_data *yd;
 	GSList *l = pkt->hash;
 	char *who = NULL, *us = NULL;
 	gboolean got_icon_info = FALSE, send_icon_info = FALSE;
@@ -104,6 +114,7 @@
 	/* Yahoo IM 6 spits out 0.png as the URL if the buddy icon is not set */
 	if (who && got_icon_info && url && !strncasecmp(url, "http://", 7)) {
 		/* TODO: make this work p2p, try p2p before the url */
+		GaimUtilFetchUrlData *url_data;
 		struct yahoo_fetch_picture_data *data;
 		GaimBuddy *b = gaim_find_buddy(gc->account, who);
 		if (b && (checksum == gaim_blist_node_get_int((GaimBlistNode*)b, YAHOO_ICON_CHECKSUM_KEY)))
@@ -113,8 +124,16 @@
 		data->gc = gc;
 		data->who = g_strdup(who);
 		data->checksum = checksum;
-		gaim_url_fetch(url, FALSE, "Mozilla/4.0 (compatible; MSIE 5.0)", FALSE,
-		               yahoo_fetch_picture_cb, data);
+		url_data = gaim_util_fetch_url(url, FALSE,
+				"Mozilla/4.0 (compatible; MSIE 5.0)", FALSE,
+				yahoo_fetch_picture_cb, data);
+		if (url_data != NULL) {
+			yd = gc->proto_data;
+			yd->url_datas = g_slist_prepend(yd->url_datas, url_data);
+		} else {
+			g_free(data->who);
+			g_free(data);
+		}
 	} else if (who && send_icon_info) {
 		yahoo_send_picture_info(gc, who);
 	}
--- a/libgaim/protocols/yahoo/yahoo_profile.c	Sun Aug 27 19:47:41 2006 +0000
+++ b/libgaim/protocols/yahoo/yahoo_profile.c	Sun Aug 27 21:13:30 2006 +0000
@@ -742,13 +742,16 @@
 	return it;
 }
 
-static void yahoo_got_photo(void *data, const char *url_text, size_t len);
+static void
+yahoo_got_photo(GaimUtilFetchUrlData *url_data, gpointer data,
+		const gchar *url_text, size_t len, const gchar *error_message);
 
 #endif /* PHOTO_SUPPORT */
 
-static void yahoo_got_info(void *data, const char *url_text, size_t len)
+static void yahoo_got_info(GaimUtilFetchUrlData *url_data, gpointer user_data,
+		const gchar *url_text, size_t len, const gchar *error_message)
 {
-	YahooGetInfoData *info_data = (YahooGetInfoData *)data;
+	YahooGetInfoData *info_data = (YahooGetInfoData *)user_data;
 	char *p;
 	char buf[1024];
 #if PHOTO_SUPPORT
@@ -771,26 +774,22 @@
 	const char *title;
 	profile_state_t profile_state = PROFILE_STATE_DEFAULT;
 
-	if (!GAIM_CONNECTION_IS_VALID(info_data->gc)) {
-		g_free(info_data->name);
-		g_free(info_data);
-		return;
-	}
-
 	gaim_debug_info("yahoo", "In yahoo_got_info\n");
 
 	yd = info_data->gc->proto_data;
-	title = (yd->jp? _("Yahoo! Japan Profile") :
-					 _("Yahoo! Profile"));
+	yd->url_datas = g_slist_remove(yd->url_datas, url_data);
+
+	title = yd->jp ? _("Yahoo! Japan Profile") :
+					 _("Yahoo! Profile");
 
 	/* Get the tooltip info string */
 	tooltip_text = yahoo_tooltip_info_text(info_data);
-	
+
 	/* We failed to grab the profile URL.  This is not expected to actually
 	 * happen except under unusual error conditions, as Yahoo is observed
 	 * to send back HTML, with a 200 status code.
 	 */
-	if (url_text == NULL || strcmp(url_text, "") == 0) {
+	if (error_message != NULL || url_text == NULL || strcmp(url_text, "") == 0) {
 		g_snprintf(buf, 1024, "<html><body>%s<b>%s</b></body></html>",
 				tooltip_text, _("Error retrieving profile"));
 
@@ -920,21 +919,32 @@
 
 	/* Try to put the photo in there too, if there's one */
 	if (photo_url_text) {
+		GaimUtilFetchUrlData *url_data;
 		/* User-uploaded photos use a different server that requires the Host
 		 * header, but Yahoo Japan will use the "chunked" content encoding if
-		 * we specify HTTP 1.1. So we have to specify 1.0 & fix gaim_url_fetch
+		 * we specify HTTP 1.1. So we have to specify 1.0 & fix gaim_util_fetch_url
 		 */
-		gaim_url_fetch(photo_url_text, FALSE, NULL, FALSE, yahoo_got_photo,
-				info2_data);
+		url_data = gaim_util_fetch_url(photo_url_text, FALSE, NULL,
+				FALSE, yahoo_got_photo, info2_data);
+		if (url_data != NULL)
+			yd->url_datas = g_slist_prepend(yd->url_datas, url_data);
+		else {
+			g_free(info2_data->info_data->name);
+			g_free(info2_data->info_data);
+			g_free(info2_data);
+		}
 	} else {
 		/* Emulate a callback */
-		yahoo_got_photo(info2_data, NULL, 0);
+		yahoo_got_photo(NULL, info2_data, NULL, 0, NULL);
 	}
 }
 
-static void yahoo_got_photo(void *data, const char *url_text, size_t len)
+static void
+yahoo_got_photo(GaimUtilFetchUrlData *url_data, gpointer data,
+		const gchar *url_text, size_t len, const gchar *error_message)
 {
 	YahooGetInfoStepTwoData *info2_data = (YahooGetInfoStepTwoData *)data;
+	struct yahoo_data *yd;
 	gboolean found = FALSE;
 	int id = -1;
 
@@ -962,6 +972,10 @@
 	/* </dd> and not \n. The prpl's need to be audited before it can be moved */
 	/* in to gaim_markup_strip_html*/
 	char *fudged_buffer;
+
+	yd = info_data->gc->proto_data;
+	yd->url_datas = g_slist_remove(yd->url_datas, url_data);
+
 	fudged_buffer = gaim_strcasereplace(url_buffer, "</dd>", "</dd><br>");
 	/* nuke the html, it's easier than trying to parse the horrid stuff */
 	stripped = gaim_markup_strip_html(fudged_buffer);
@@ -1197,7 +1211,6 @@
 					  "however, Yahoo! sometimes does fail to find a user's "
 					  "profile. If you know that the user exists, "
 					  "please try again later."));
-		
 		} else {
 			g_string_append_printf(s, "%s<br><br>",
 					_("The user's profile is empty."));
@@ -1245,15 +1258,22 @@
 	struct yahoo_data *yd = gc->proto_data;
 	YahooGetInfoData *data;
 	char *url;
+	GaimUtilFetchUrlData *url_data;
 
 	data       = g_new0(YahooGetInfoData, 1);
 	data->gc   = gc;
 	data->name = g_strdup(name);
 
 	url = g_strdup_printf("%s%s",
-			(yd->jp? YAHOOJP_PROFILE_URL: YAHOO_PROFILE_URL), name);
+			(yd->jp ? YAHOOJP_PROFILE_URL : YAHOO_PROFILE_URL), name);
 
-	gaim_url_fetch(url, TRUE, NULL, FALSE, yahoo_got_info, data);
+	url_data = gaim_util_fetch_url(url, TRUE, NULL, FALSE, yahoo_got_info, data);
+	if (url_data != NULL)
+		yd->url_datas = g_slist_prepend(yd->url_datas, url_data);
+	else {
+		g_free(data->name);
+		g_free(data);
+	}
 
 	g_free(url);
 }
--- a/libgaim/upnp.c	Sun Aug 27 19:47:41 2006 +0000
+++ b/libgaim/upnp.c	Sun Aug 27 21:13:30 2006 +0000
@@ -150,7 +150,6 @@
 static void lookup_public_ip(void);
 static void lookup_internal_ip(void);
 
-
 static void
 fire_discovery_callbacks(gboolean success)
 {
@@ -164,7 +163,6 @@
 	}
 }
 
-
 static gboolean
 gaim_upnp_compare_device(const xmlnode* device, const gchar* deviceType)
 {
@@ -183,7 +181,6 @@
 	return ret;
 }
 
-
 static gboolean
 gaim_upnp_compare_service(const xmlnode* service, const gchar* serviceType)
 {
@@ -208,7 +205,6 @@
 	return ret;
 }
 
-
 static gchar*
 gaim_upnp_parse_description_response(const gchar* httpResponse, gsize len,
 	const gchar* httpURL, const gchar* serviceType)
@@ -358,9 +354,10 @@
 }
 
 static void
-upnp_parse_description_cb(void *data, const char *httpResponse, gsize len)
+upnp_parse_description_cb(GaimUtilFetchUrlData *url_data, gpointer user_data,
+		const gchar *httpResponse, gsize len, const gchar *error_message)
 {
-	UPnPDiscoveryData *dd = data;
+	UPnPDiscoveryData *dd = user_data;
 	gchar *control_url = NULL;
 
 	if (len > 0)
@@ -431,7 +428,7 @@
 	gaim_timeout_remove(dd->tima);
 	dd->tima = 0;
 
-	gaim_url_fetch_request(descriptionURL, TRUE, NULL, TRUE, httpRequest,
+	gaim_util_fetch_url_request(descriptionURL, TRUE, NULL, TRUE, httpRequest,
 			TRUE, upnp_parse_description_cb, dd);
 
 	g_free(httpRequest);
@@ -548,7 +545,7 @@
 	/* We'll either time out or continue successfully */
 }
 
-void
+static void
 gaim_upnp_discover_send_broadcast(UPnPDiscoveryData *dd)
 {
 	gchar *sendMessage = NULL;
@@ -598,7 +595,6 @@
 	gaim_timeout_add(10, gaim_upnp_discover_timeout, dd);
 }
 
-
 void
 gaim_upnp_discover(GaimUPnPCallback cb, gpointer cb_data)
 {
@@ -661,7 +657,7 @@
 
 static void
 gaim_upnp_generate_action_message_and_send(const gchar* actionName,
-		const gchar* actionParams, GaimURLFetchCallback cb,
+		const gchar* actionParams, GaimUtilFetchUrlCallback cb,
 		gpointer cb_data)
 {
 
@@ -678,7 +674,7 @@
 			"generate_action_message_and_send(): Failed In Parse URL\n\n");
 		/* XXX: This should probably be async */
 		if(cb)
-			cb(cb_data, NULL, 0);
+			cb(NULL, cb_data, NULL, 0, NULL);
 	}
 	if(port == 0 || port == -1) {
 		port = DEFAULT_HTTP_PORT;
@@ -696,14 +692,13 @@
 	g_free(pathOfControl);
 	g_free(soapMessage);
 
-	gaim_url_fetch_request(control_info.control_url, FALSE, NULL, TRUE,
+	gaim_util_fetch_url_request(control_info.control_url, FALSE, NULL, TRUE,
 			totalSendMessage, TRUE, cb, cb_data);
 
 	g_free(totalSendMessage);
 	g_free(addressOfControl);
 }
 
-
 const gchar *
 gaim_upnp_get_public_ip()
 {
@@ -722,11 +717,12 @@
 }
 
 static void
-looked_up_public_ip_cb(gpointer data, const char *httpResponse, gsize len)
+looked_up_public_ip_cb(GaimUtilFetchUrlData *url_data, gpointer user_data,
+		const gchar *httpResponse, gsize len, const gchar *error_message)
 {
 	gchar* temp, *temp2;
 
-	if(!httpResponse)
+	if ((error_message != NULL) || (httpResponse == NULL))
 		return;
 
 	/* extract the ip, or see if there is an error */
@@ -754,7 +750,7 @@
 	gaim_debug_info("upnp", "NAT Returned IP: %s\n", control_info.publicip);
 }
 
-void
+static void
 lookup_public_ip()
 {
 	gaim_upnp_generate_action_message_and_send("GetExternalIPAddress", "",
@@ -794,7 +790,7 @@
 
 }
 
-void
+static void
 lookup_internal_ip()
 {
 	gchar* addressOfControl;
@@ -821,14 +817,17 @@
 }
 
 static void
-done_port_mapping_cb(gpointer data, const gchar *httpResponse, gsize len)
+done_port_mapping_cb(GaimUtilFetchUrlData *url_data, gpointer user_data,
+		const gchar *httpResponse, gsize len, const gchar *error_message)
 {
-	UPnPMappingAddRemove *ar = data;
+	UPnPMappingAddRemove *ar = user_data;
 
 	gboolean success = TRUE;
 
 	/* determine if port mapping was a success */
-	if(!httpResponse || g_strstr_len(httpResponse, len, HTTP_OK) == NULL) {
+	if ((error_message != NULL) || (httpResponse == NULL) ||
+		(g_strstr_len(httpResponse, len, HTTP_OK) == NULL))
+	{
 		gaim_debug_error("upnp",
 			"gaim_upnp_set_port_mapping(): Failed HTTP_OK\n\n%s\n\n",
 			httpResponse ? httpResponse : "(null)");
--- a/libgaim/util.c	Sun Aug 27 19:47:41 2006 +0000
+++ b/libgaim/util.c	Sun Aug 27 21:13:30 2006 +0000
@@ -29,9 +29,9 @@
 #include "prefs.h"
 #include "util.h"
 
-typedef struct
+struct _GaimUtilFetchUrlData
 {
-	void (*callback)(void *, const char *, size_t);
+	GaimUtilFetchUrlCallback callback;
 	void *user_data;
 
 	struct
@@ -52,15 +52,16 @@
 	gsize request_written;
 	gboolean include_headers;
 
-	int inpa;
+	GaimProxyConnectData *connect_data;
+	int fd;
+	guint inpa;
 
 	gboolean got_headers;
 	gboolean has_explicit_data_len;
 	char *webdata;
 	unsigned long len;
 	unsigned long data_len;
-
-} GaimFetchUrlData;
+};
 
 static char custom_home_dir[MAXPATHLEN];
 static char home_dir[MAXPATHLEN];
@@ -3059,24 +3060,28 @@
 	return TRUE;
 }
 
+/**
+ * The arguments to this function are similar to printf.
+ */
 static void
-destroy_fetch_url_data(GaimFetchUrlData *gfud)
+gaim_util_fetch_url_error(GaimUtilFetchUrlData *gfud, const char *format, ...)
 {
-	g_free(gfud->webdata);
-	g_free(gfud->url);
-	g_free(gfud->user_agent);
-	g_free(gfud->website.address);
-	g_free(gfud->website.page);
-	g_free(gfud->website.user);
-	g_free(gfud->website.passwd);
-	g_free(gfud->request);
-
-	g_free(gfud);
+	gchar *error_message;
+	va_list args;
+
+	va_start(args, format);
+	error_message = g_strdup_vprintf(format, args);
+	va_end(args);
+
+	gfud->callback(gfud, gfud->user_data, NULL, 0, error_message);
+	g_free(error_message);
+	gaim_util_fetch_url_cancel(gfud);
 }
 
+/* TODO: This totally destroys cancelability. */
 static gboolean
 parse_redirect(const char *data, size_t data_len, gint sock,
-			   GaimFetchUrlData *gfud)
+			   GaimUtilFetchUrlData *gfud)
 {
 	gchar *s;
 
@@ -3116,20 +3121,16 @@
 			full = FALSE;
 		}
 
-		/* Close the existing stuff. */
-		gaim_input_remove(gfud->inpa);
-		close(sock);
-
-		gaim_debug_info("gaim_url_fetch", "Redirecting to %s\n", new_url);
+		gaim_debug_info("util", "Redirecting to %s\n", new_url);
 
 		/* Try again, with this new location. */
-		gaim_url_fetch_request(new_url, full, gfud->user_agent,
+		gaim_util_fetch_url_request(new_url, full, gfud->user_agent,
 				gfud->http11, NULL, gfud->include_headers,
 				gfud->callback, gfud->user_data);
 
-		/* Free up. */
+		/* Free the old connection */
 		g_free(new_url);
-		destroy_fetch_url_data(gfud);
+		gaim_util_fetch_url_cancel(gfud);
 
 		return TRUE;
 	}
@@ -3173,7 +3174,7 @@
 	 */
 	if (p && g_strstr_len(p, data_len - (p - data), "\n")) {
 		sscanf(p, "%" G_GSIZE_FORMAT, &content_len);
-		gaim_debug_misc("parse_content_len", "parsed %u\n", content_len);
+		gaim_debug_misc("util", "parsed %u\n", content_len);
 	}
 
 	return content_len;
@@ -3183,14 +3184,14 @@
 static void
 url_fetch_recv_cb(gpointer url_data, gint source, GaimInputCondition cond)
 {
-	GaimFetchUrlData *gfud = url_data;
+	GaimUtilFetchUrlData *gfud = url_data;
 	int len;
 	char buf[4096];
 	char *data_cursor;
 	gboolean got_eof = FALSE;
 
 	while((len = read(source, buf, sizeof(buf))) > 0) {
-		/* If we've filled up our butfer, make it bigger */
+		/* If we've filled up our buffer, make it bigger */
 		if((gfud->len + len) >= gfud->data_len) {
 			while((gfud->len + len) >= gfud->data_len)
 				gfud->data_len += sizeof(buf);
@@ -3209,13 +3210,13 @@
 		if(!gfud->got_headers) {
 			char *tmp;
 
-			/** See if we've reached the end of the headers yet */
+			/* See if we've reached the end of the headers yet */
 			if((tmp = strstr(gfud->webdata, "\r\n\r\n"))) {
 				char * new_data;
 				guint header_len = (tmp + 4 - gfud->webdata);
 				size_t content_len;
 
-				gaim_debug_misc("gaim_url_fetch", "Response headers: '%.*s'\n",
+				gaim_debug_misc("util", "Response headers: '%.*s'\n",
 					header_len, gfud->webdata);
 
 				/* See if we can find a redirect. */
@@ -3249,12 +3250,14 @@
 
 					new_data = g_try_malloc(content_len);
 					if(new_data == NULL) {
-						gaim_debug_error("gaim_url_fetch", "Failed to allocate %u bytes: %s\n",
-							content_len, strerror(errno));
-						gaim_input_remove(gfud->inpa);
-						close(source);
-						gfud->callback(gfud->user_data, NULL, 0);
-						destroy_fetch_url_data(gfud);
+						gaim_debug_error("util",
+								"Failed to allocate %u bytes: %s\n",
+								content_len, strerror(errno));
+						gaim_util_fetch_url_error(gfud,
+								_("Unable to allocate enough memory to hold "
+								  "the contents from %s.  The web server may "
+								  "be trying something malicious."),
+								gfud->website.address);
 
 						return;
 					}
@@ -3288,12 +3291,8 @@
 		} else if(errno != ETIMEDOUT) {
 			got_eof = TRUE;
 		} else {
-			gaim_input_remove(gfud->inpa);
-			close(source);
-
-			gfud->callback(gfud->user_data, NULL, 0);
-
-			destroy_fetch_url_data(gfud);
+			gaim_util_fetch_url_error(gfud, _("Error reading from %s: %s"),
+					gfud->website.address, strerror(errno));
 			return;
 		}
 	}
@@ -3302,63 +3301,59 @@
 		gfud->webdata = g_realloc(gfud->webdata, gfud->len + 1);
 		gfud->webdata[gfud->len] = '\0';
 
-		/* gaim_debug_misc("gaim_url_fetch", "Received: '%s'\n", gfud->webdata); */
-
-		gaim_input_remove(gfud->inpa);
-		close(source);
-		gfud->callback(gfud->user_data, gfud->webdata, gfud->len);
-
-		destroy_fetch_url_data(gfud);
+		gfud->callback(gfud, gfud->user_data, gfud->webdata, gfud->len, NULL);
+		gaim_util_fetch_url_cancel(gfud);
 	}
 }
 
 static void
 url_fetch_send_cb(gpointer data, gint source, GaimInputCondition cond)
 {
-	GaimFetchUrlData *gfud;
+	GaimUtilFetchUrlData *gfud;
 	int len, total_len;
 
 	gfud = data;
 
 	total_len = strlen(gfud->request);
 
-	len = write(source, gfud->request + gfud->request_written,
+	len = write(gfud->fd, gfud->request + gfud->request_written,
 			total_len - gfud->request_written);
 
-	if(len < 0 && errno == EAGAIN)
+	if (len < 0 && errno == EAGAIN)
 		return;
-	else if(len < 0) {
-		gaim_input_remove(gfud->inpa);
-		close(source);
-		gfud->callback(gfud->user_data, NULL, 0);
-		destroy_fetch_url_data(gfud);
+	else if (len < 0) {
+		gaim_util_fetch_url_error(gfud, _("Error writing to %s: %s"),
+				gfud->website.address, strerror(errno));
 		return;
 	}
 	gfud->request_written += len;
 
-	if(gfud->request_written != total_len)
+	if (gfud->request_written != total_len)
 		return;
 
-	/* We're done writing, now start reading */
+	/* We're done writing our request, now start reading the response */
 	gaim_input_remove(gfud->inpa);
-	gfud->inpa = gaim_input_add(source, GAIM_INPUT_READ, url_fetch_recv_cb,
+	gfud->inpa = gaim_input_add(gfud->fd, GAIM_INPUT_READ, url_fetch_recv_cb,
 		gfud);
 }
 
 static void
 url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message)
 {
-	GaimFetchUrlData *gfud;
+	GaimUtilFetchUrlData *gfud;
 
 	gfud = url_data;
+	gfud->connect_data = NULL;
 
 	if (source == -1)
 	{
-		gfud->callback(gfud->user_data, NULL, 0);
-		destroy_fetch_url_data(gfud);
+		gaim_util_fetch_url_error(gfud, _("Unable to connect to %s: %s"),
+				gfud->website.address, error_message);
 		return;
 	}
 
+	gfud->fd = source;
+
 	if (!gfud->request)
 	{
 		if (gfud->user_agent) {
@@ -3390,31 +3385,31 @@
 		}
 	}
 
-	gaim_debug_misc("gaim_url_fetch", "Request: '%s'\n", gfud->request);
+	gaim_debug_misc("util", "Request: '%s'\n", gfud->request);
 
 	gfud->inpa = gaim_input_add(source, GAIM_INPUT_WRITE,
 								url_fetch_send_cb, gfud);
 	url_fetch_send_cb(gfud, source, GAIM_INPUT_WRITE);
 }
 
-void
-gaim_url_fetch_request(const char *url, gboolean full,
+GaimUtilFetchUrlData *
+gaim_util_fetch_url_request(const char *url, gboolean full,
 		const char *user_agent, gboolean http11,
 		const char *request, gboolean include_headers,
-		GaimURLFetchCallback cb, void *user_data)
+		GaimUtilFetchUrlCallback callback, void *user_data)
 {
-	GaimFetchUrlData *gfud;
-
-	g_return_if_fail(url != NULL);
-	g_return_if_fail(cb  != NULL);
-
-	gaim_debug_info("gaim_url_fetch",
+	GaimUtilFetchUrlData *gfud;
+
+	g_return_val_if_fail(url      != NULL, NULL);
+	g_return_val_if_fail(callback != NULL, NULL);
+
+	gaim_debug_info("util",
 			 "requested to fetch (%s), full=%d, user_agent=(%s), http11=%d\n",
 			 url, full, user_agent?user_agent:"(null)", http11);
 
-	gfud = g_new0(GaimFetchUrlData, 1);
-
-	gfud->callback = cb;
+	gfud = g_new0(GaimUtilFetchUrlData, 1);
+
+	gfud->callback = callback;
 	gfud->user_data  = user_data;
 	gfud->url = g_strdup(url);
 	gfud->user_agent = g_strdup(user_agent);
@@ -3426,13 +3421,42 @@
 	gaim_url_parse(url, &gfud->website.address, &gfud->website.port,
 				   &gfud->website.page, &gfud->website.user, &gfud->website.passwd);
 
-	if (gaim_proxy_connect(NULL, gfud->website.address,
-		gfud->website.port, url_fetch_connect_cb, gfud) == NULL)
+	gfud->connect_data = gaim_proxy_connect(NULL,
+			gfud->website.address, gfud->website.port,
+			url_fetch_connect_cb, gfud);
+
+	if (gfud->connect_data == NULL)
 	{
-		destroy_fetch_url_data(gfud);
-
-		cb(user_data, g_strdup(_("g003: Error opening connection.\n")), 0);
+		gaim_util_fetch_url_error(gfud, _("Unable to connect to %s"),
+				gfud->website.address);
+		return NULL;
 	}
+
+	return gfud;
+}
+
+void
+gaim_util_fetch_url_cancel(GaimUtilFetchUrlData *gfud)
+{
+	if (gfud->connect_data != NULL)
+		gaim_proxy_connect_cancel(gfud->connect_data);
+
+	if (gfud->inpa > 0)
+		gaim_input_remove(gfud->inpa);
+
+	if (gfud->fd >= 0)
+		close(gfud->fd);
+
+	g_free(gfud->website.user);
+	g_free(gfud->website.passwd);
+	g_free(gfud->website.address);
+	g_free(gfud->website.page);
+	g_free(gfud->url);
+	g_free(gfud->user_agent);
+	g_free(gfud->request);
+	g_free(gfud->webdata);
+
+	g_free(gfud);
 }
 
 const char *
--- a/libgaim/util.h	Sun Aug 27 19:47:41 2006 +0000
+++ b/libgaim/util.h	Sun Aug 27 21:13:30 2006 +0000
@@ -37,6 +37,8 @@
 extern "C" {
 #endif
 
+typedef struct _GaimUtilFetchUrlData GaimUtilFetchUrlData;
+
 typedef struct _GaimMenuAction
 {
 	char *label;
@@ -823,7 +825,22 @@
 gboolean gaim_url_parse(const char *url, char **ret_host, int *ret_port,
 						char **ret_path, char **ret_user, char **ret_passwd);
 
-typedef void (*GaimURLFetchCallback) (gpointer data, const char *buf, gsize len);
+/**
+ * This is the signature used for functions that act as the callback
+ * to gaim_util_fetch_url() or gaim_util_fetch_url_request().
+ *
+ * @param url_data      The same value that was returned when you called
+ *                      gaim_fetch_url() or gaim_fetch_url_request().
+ * @param user_data     The user data that your code passed into either
+ *                      gaim_util_fetch_url() or gaim_util_fetch_url_request().
+ * @param url_text      This will be NULL on error.  Otherwise this
+ *                      will contain the contents of the URL.
+ * @param len           0 on error, otherwise this is the length of buf.
+ * @param error_message If something went wrong then this will contain
+ *                      a descriptive error message, and buf will be
+ *                      NULL and len will be 0.
+ */
+typedef void (*GaimUtilFetchUrlCallback)(GaimUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message);
 
 /**
  * Fetches the data from a URL, and passes it to a callback function.
@@ -836,8 +853,8 @@
  * @param cb         The callback function.
  * @param data       The user data to pass to the callback function.
  */
-#define gaim_url_fetch(url, full, user_agent, http11, cb, data) \
-	gaim_url_fetch_request(url, full, user_agent, http11, NULL, \
+#define gaim_util_fetch_url(url, full, user_agent, http11, cb, data) \
+	gaim_util_fetch_url_request(url, full, user_agent, http11, NULL, \
 		FALSE, cb, data);
 
 /**
@@ -850,15 +867,23 @@
  * @param http11     TRUE if HTTP/1.1 should be used to download the file.
  * @param request    A HTTP request to send to the server instead of the
  *                   standard GET
- * @param include_headers if TRUE, include the HTTP headers in the
- *                   response
- * @param cb         The callback function.
+ * @param include_headers
+ *                   If TRUE, include the HTTP headers in the response.
+ * @param callback   The callback function.
  * @param data       The user data to pass to the callback function.
  */
-void gaim_url_fetch_request(const char *url, gboolean full,
-		const char *user_agent, gboolean http11,
-		const char *request, gboolean include_headers,
-		GaimURLFetchCallback cb, void *data);
+GaimUtilFetchUrlData *gaim_util_fetch_url_request(const gchar *url,
+		gboolean full, const gchar *user_agent, gboolean http11,
+		const gchar *request, gboolean include_headers,
+		GaimUtilFetchUrlCallback callback, gpointer data);
+
+/**
+ * Cancel a pending URL request started with either
+ * gaim_util_fetch_url_request() or gaim_util_fetch_url().
+ *
+ * @param url_data The data returned when you initiated the URL fetch.
+ */
+void gaim_util_fetch_url_cancel(GaimUtilFetchUrlData *url_data);
 
 /**
  * Decodes a URL into a plain string.