diff libgaim/util.c @ 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 cf8d25072151
children 646dcf11b4eb
line wrap: on
line diff
--- 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 *