changeset 1840:00aef397a1fe

[gaim-migrate @ 1850] reworked some of the proxy stuff so that it's non-blocking now. next thing to do is to get IRC, MSN, Napster, and Jabber to use the new proxy_connect code. After that, Oscar and Yahoo (maybe Zephyr too? not likely) committer: Tailor Script <tailor@pidgin.im>
author Eric Warmenhoven <eric@warmenhoven.org>
date Sat, 12 May 2001 01:38:04 +0000
parents 109cacf1ff97
children 93f0629d4099
files src/dialogs.c src/gaim.h src/html.c src/prefs.c src/proxy.c src/proxy.h src/toc.c
diffstat 7 files changed, 612 insertions(+), 213 deletions(-) [+]
line wrap: on
line diff
--- a/src/dialogs.c	Fri May 11 21:54:27 2001 +0000
+++ b/src/dialogs.c	Sat May 12 01:38:04 2001 +0000
@@ -1784,16 +1784,6 @@
 	gtk_adjustment_set_value(gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sw)), 0);
 }
 
-void g_show_info(struct aim_user *user, char *url) {
-	char *url_text = grab_url(user, url);
-	if (connections)
-		g_show_info_text(away_subs(url_text,
-					((struct gaim_connection *)connections->data)->username));
-	else
-		g_show_info_text(url_text);
-	g_free(url_text);
-}
-
 /*------------------------------------------------------------------------*/
 /*  The dialog for adding to permit/deny                                  */
 /*------------------------------------------------------------------------*/
--- a/src/gaim.h	Fri May 11 21:54:27 2001 +0000
+++ b/src/gaim.h	Sat May 12 01:38:04 2001 +0000
@@ -590,7 +590,7 @@
 
 /* Functions in html.c */
 extern struct g_url parse_url(char *);
-extern char *grab_url(struct aim_user *, char *);
+extern void grab_url(struct aim_user *, char *, void (*callback)(gpointer, char *), gpointer);
 extern gchar *strip_html(gchar *);
 
 /* Functions in idle.c */
@@ -838,8 +838,7 @@
 extern void show_log_dialog(struct conversation *);
 extern void show_find_email(struct gaim_connection *gc);
 extern void show_find_info();
-extern void g_show_info (struct aim_user *, char *);
-extern void g_show_info_text (char *);
+extern void g_show_info_text(char *);
 extern void show_set_info(struct gaim_connection *);
 extern void show_set_dir();
 extern void show_fgcolor_dialog(struct conversation *c, GtkWidget *color);
--- a/src/html.c	Fri May 11 21:54:27 2001 +0000
+++ b/src/html.c	Sat May 12 01:38:04 2001 +0000
@@ -100,11 +100,17 @@
 	return test;
 }
 
-char *grab_url(struct aim_user *user, char *url)
-{
+struct grab_url_data {
+	void (*callback)(gpointer, char *);
+	gpointer data;
 	struct g_url website;
+	char *url;
+};
+
+static void grab_url_callback(gpointer dat, gint sock, GdkInputCondition cond)
+{
+	struct grab_url_data *gunk = dat;
 	char *webdata = NULL;
-	int sock;
 	int len;
 	int read_rv;
 	int datalen = 0;
@@ -113,18 +119,14 @@
 	int startsaving = 0;
 	GtkWidget *pw = NULL, *pbar = NULL, *label;
 
-	website = parse_url(url);
-
-	if (user) {
-		if ((sock = proxy_connect(website.address, website.port, user->proto_opt[2],
-					  atoi(user->proto_opt[3]), atoi(user->proto_opt[4]))) < 0)
-			return g_strdup(_("g003: Error opening connection.\n"));
-	} else {
-		if ((sock = proxy_connect(website.address, website.port, NULL, 0, -1)) < 0)
-			return g_strdup(_("g003: Error opening connection.\n"));
+	if (sock == -1) {
+		gunk->callback(gunk->data, NULL);
+		g_free(gunk->url);
+		g_free(gunk);
+		return;
 	}
 
-	g_snprintf(buf, sizeof(buf), "GET /%s HTTP/1.0\r\n\r\n", website.page);
+	g_snprintf(buf, sizeof(buf), "GET /%s HTTP/1.0\r\n\r\n", gunk->website.page);
 	debug_printf("Request: %s\n", buf);
 	write(sock, buf, strlen(buf));
 	fcntl(sock, F_SETFL, O_NONBLOCK);
@@ -153,7 +155,8 @@
 				char tmpbuf[1024];
 				sscanf(cs, "Content-Length: %d", &datalen);
 
-				g_snprintf(tmpbuf, 1024, _("Getting %d bytes from %s"), datalen, url);
+				g_snprintf(tmpbuf, 1024, _("Getting %d bytes from %s"),
+						datalen, gunk->url);
 				pw = gtk_dialog_new();
 
 				label = gtk_label_new(tmpbuf);
@@ -204,5 +207,36 @@
 		gtk_widget_destroy(pw);
 
 	close(sock);
-	return webdata;
+	gunk->callback(gunk->data, webdata);
+	g_free(gunk->url);
+	g_free(gunk);
 }
+
+void grab_url(struct aim_user *user, char *url, void (*callback)(gpointer, char *), gpointer data)
+{
+	int sock;
+	struct grab_url_data *gunk = g_new0(struct grab_url_data, 1);
+
+	gunk->callback = callback;
+	gunk->data = data;
+	gunk->url = g_strdup(url);
+	gunk->website = parse_url(url);
+
+	if (user) {
+		if ((sock = proxy_connect(gunk->website.address, gunk->website.port,
+					  user->proto_opt[2], atoi(user->proto_opt[3]),
+					  atoi(user->proto_opt[4]),
+					  grab_url_callback, gunk)) < 0) {
+			g_free(gunk->url);
+			g_free(gunk);
+			callback(data, g_strdup(_("g003: Error opening connection.\n")));
+		}
+	} else {
+		if ((sock = proxy_connect(gunk->website.address, gunk->website.port, NULL, 0, -1,
+						grab_url_callback, gunk)) < 0) {
+			g_free(gunk->url);
+			g_free(gunk);
+			callback(data, g_strdup(_("g003: Error opening connection.\n")));
+		}
+	}
+}
--- a/src/prefs.c	Fri May 11 21:54:27 2001 +0000
+++ b/src/prefs.c	Sat May 12 01:38:04 2001 +0000
@@ -841,16 +841,19 @@
 
 static struct chat_page *cp = NULL;
 
-static void refresh_list(GtkWidget *w, gpointer *m)
-{
-	char *text = grab_url(NULL, "http://www.aol.com/community/chat/allchats.html");
+static void ref_list_callback(gpointer data, char *text) {
 	char *c;
-	int len = strlen(text);
+	int len;
 	GtkWidget *item;
 	GList *items = GTK_LIST(cp->list1)->children;
 	struct chat_room *cr;
 	c = text;
 
+	if (!text)
+		return;
+
+	len = strlen(text);
+
 	while (items) {
 		g_free(gtk_object_get_user_data(GTK_OBJECT(items->data)));
 		items = items->next;
@@ -909,6 +912,11 @@
 	g_free(text);
 }
 
+static void refresh_list(GtkWidget *w, gpointer *m)
+{
+	grab_url(NULL, "http://www.aol.com/community/chat/allchats.html", ref_list_callback, NULL);
+}
+
 static void add_chat(GtkWidget *w, gpointer *m)
 {
 	GList *sel = GTK_LIST(cp->list1)->selection;
--- a/src/proxy.c	Fri May 11 21:54:27 2001 +0000
+++ b/src/proxy.c	Sat May 12 01:38:04 2001 +0000
@@ -34,11 +34,40 @@
 #include <netdb.h>
 #include <netinet/in.h>
 #include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
 #include <gtk/gtk.h>
 #include "gaim.h"
 #include "proxy.h"
 
-static int proxy_connect_none(char *host, unsigned short port)
+struct PHB {
+	GdkInputFunction func;
+	gpointer data;
+	char *host;
+	int port;
+	gint inpa;
+};
+
+static void no_one_calls(gpointer data, gint source, GdkInputCondition cond)
+{
+	struct PHB *phb = data;
+	int len, error = ETIMEDOUT;
+	debug_printf("Connected\n");
+	len = sizeof(error);
+	if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
+		close(source);
+		gdk_input_remove(phb->inpa);
+		phb->func(phb->data, -1, GDK_INPUT_READ);
+		g_free(phb);
+		return;
+	}
+	fcntl(source, F_SETFL, 0);
+	gdk_input_remove(phb->inpa);
+	phb->func(phb->data, source, GDK_INPUT_READ);
+	g_free(phb);
+}
+
+static int proxy_connect_none(char *host, unsigned short port, struct PHB *phb)
 {
 	struct sockaddr_in sin;
 	struct hostent *hp;
@@ -46,20 +75,43 @@
 
 	debug_printf("connecting to %s:%d with no proxy\n", host, port);
 
-	if (!(hp = gethostbyname(host)))
+	if (!(hp = gethostbyname(host))) {
+		g_free(phb);
 		return -1;
+	}
 
 	memset(&sin, 0, sizeof(struct sockaddr_in));
 	memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
 	sin.sin_family = hp->h_addrtype;
 	sin.sin_port = htons(port);
 
-	if ((fd = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0)
+	if ((fd = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0) {
+		g_free(phb);
 		return -1;
+	}
 
+	fcntl(fd, F_SETFL, O_NONBLOCK);
 	if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
-		close(fd);
-		return -1;
+		if ((errno == EINPROGRESS) || (errno == EINTR)) {
+			debug_printf("Connect would have blocked\n");
+			phb->inpa = gdk_input_add(fd, GDK_INPUT_WRITE, no_one_calls, phb);
+		} else {
+			close(fd);
+			g_free(phb);
+			return -1;
+		}
+	} else {
+		int len, error = ETIMEDOUT;
+		debug_printf("Connect didn't block\n");
+		len = sizeof(error);
+		if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
+			close(fd);
+			g_free(phb);
+			return -1;
+		}
+		fcntl(fd, F_SETFL, 0);
+		phb->func(phb->data, fd, GDK_INPUT_READ);
+		g_free(phb);
 	}
 
 	return fd;
@@ -68,133 +120,345 @@
 #define HTTP_GOODSTRING "HTTP/1.0 200 Connection established"
 #define HTTP_GOODSTRING2 "HTTP/1.1 200 Connection established"
 
-static int proxy_connect_http(char *host, unsigned short port, char *proxyhost, unsigned short proxyport)
+static void http_canread(gpointer data, gint source, GdkInputCondition cond)
+{
+	int nlc = 0;
+	int pos = 0;
+	struct PHB *phb = data;
+	char inputline[8192];
+
+	gdk_input_remove(phb->inpa);
+
+	while ((nlc != 2) && (read(source, &inputline[pos++], 1) == 1)) {
+		if (inputline[pos-1] == '\n')
+			nlc++;
+		else if (inputline[pos-1] != '\r')
+			nlc = 0;
+	}
+	inputline[pos] = '\0';
+
+	debug_printf("Proxy says: %s\n", inputline);
+
+	if ((memcmp(HTTP_GOODSTRING , inputline, strlen(HTTP_GOODSTRING )) == 0) ||
+	    (memcmp(HTTP_GOODSTRING2, inputline, strlen(HTTP_GOODSTRING2)) == 0)) {
+		phb->func(phb->data, source, GDK_INPUT_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+
+	close(source);
+	phb->func(phb->data, -1, GDK_INPUT_READ);
+	g_free(phb->host);
+	g_free(phb);
+	return;
+}
+
+static void http_canwrite(gpointer data, gint source, GdkInputCondition cond)
+{
+	char cmd[384];
+	struct PHB *phb = data;
+	int len, error = ETIMEDOUT;
+	debug_printf("Connected\n");
+	if (phb->inpa > 0)
+		gdk_input_remove(phb->inpa);
+	len = sizeof(error);
+	if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
+		close(source);
+		phb->func(phb->data, -1, GDK_INPUT_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+	fcntl(source, F_SETFL, 0);
+
+	snprintf(cmd, sizeof(cmd), "CONNECT %s:%d HTTP/1.1\r\n\r\n", phb->host, phb->port);
+	if (send(source, cmd, strlen(cmd), 0) < 0) {
+		close(source);
+		phb->func(phb->data, -1, GDK_INPUT_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+
+	phb->inpa = gdk_input_add(source, GDK_INPUT_READ, http_canread, phb);
+}
+
+static int proxy_connect_http(char *host, unsigned short port,
+			      char *proxyhost, unsigned short proxyport,
+			      struct PHB *phb)
 {
 	struct hostent *hp;
 	struct sockaddr_in sin;
 	int fd = -1;
-	char cmd[384];
-	char inputline[8192];
-	int nlc = 0;
-	int pos = 0;
 
 	debug_printf("connecting to %s:%d via %s:%d using HTTP\n", host, port, proxyhost, proxyport);
 
-	if (!(hp = gethostbyname(proxyhost)))
+	if (!(hp = gethostbyname(proxyhost))) {
+		g_free(phb);
 		return -1;
+	}
 
 	memset(&sin, 0, sizeof(struct sockaddr_in));
 	memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
 	sin.sin_family = hp->h_addrtype;
 	sin.sin_port = htons(proxyport);
 
-	if ((fd = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0)
-		return -1;
-
-	if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
-		close(fd);
+	if ((fd = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0) {
+		g_free(phb);
 		return -1;
 	}
 
-	snprintf(cmd, sizeof(cmd), "CONNECT %s:%d HTTP/1.1\r\n\r\n", host, port);
+	phb->host = g_strdup(host);
+	phb->port = port;
 
-	if (send(fd, cmd, strlen(cmd), 0) < 0) {
-		close(fd);
-		return -1;
+	fcntl(fd, F_SETFL, O_NONBLOCK);
+	if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+		if ((errno == EINPROGRESS) || (errno == EINTR)) {
+			debug_printf("Connect would have blocked\n");
+			phb->inpa = gdk_input_add(fd, GDK_INPUT_WRITE, http_canwrite, phb);
+		} else {
+			close(fd);
+			g_free(phb->host);
+			g_free(phb);
+			return -1;
+		}
+	} else {
+		int len, error = ETIMEDOUT;
+		debug_printf("Connect didn't block\n");
+		len = sizeof(error);
+		if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
+			close(fd);
+			g_free(phb->host);
+			g_free(phb);
+			return -1;
+		}
+		fcntl(fd, F_SETFL, 0);
+		http_canwrite(phb, fd, GDK_INPUT_WRITE);
 	}
-	while ((nlc != 2) && (read(fd, &inputline[pos++], 1) == 1)) {
-		if (inputline[pos-1] == '\n')
-			nlc++;
-		else if (inputline[pos-1] != '\r')
-			nlc = 0;
+
+	return fd;
+}
+
+static void s4_canread(gpointer data, gint source, GdkInputCondition cond)
+{
+	unsigned char packet[12];
+	struct PHB *phb = data;
+
+	gdk_input_remove(phb->inpa);
+
+	memset(packet, 0, sizeof(packet));
+	if (read(source, packet, 9) >= 4 && packet[1] == 90) {
+		phb->func(phb->data, source, GDK_INPUT_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return;
 	}
 
-	debug_printf("Proxy says: %s\n", inputline);
+	close(source);
+	phb->func(phb->data, -1, GDK_INPUT_READ);
+	g_free(phb->host);
+	g_free(phb);
+}
 
-	if ((memcmp(HTTP_GOODSTRING, inputline, strlen(HTTP_GOODSTRING)) == 0) ||
-	     (memcmp(HTTP_GOODSTRING2, inputline, strlen(HTTP_GOODSTRING2)) == 0)) {
-		return fd;
+static void s4_canwrite(gpointer data, gint source, GdkInputCondition cond)
+{
+	unsigned char packet[12];
+	struct hostent *hp;
+	struct PHB *phb = data;
+	int len, error = ETIMEDOUT;
+	debug_printf("Connected\n");
+	if (phb->inpa > 0)
+		gdk_input_remove(phb->inpa);
+	len = sizeof(error);
+	if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
+		close(source);
+		phb->func(phb->data, -1, GDK_INPUT_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+	fcntl(source, F_SETFL, 0);
+
+	/* XXX does socks4 not support host name lookups by the proxy? */
+	if (!(hp = gethostbyname(phb->host))) {
+		close(source);
+		phb->func(phb->data, -1, GDK_INPUT_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return;
 	}
 
-	close(fd);
-	return -1;
+	packet[0] = 4;
+	packet[1] = 1;
+	packet[2] = phb->port >> 8;
+	packet[3] = phb->port & 0xff;
+	packet[4] = (unsigned char)(hp->h_addr_list[0])[0];
+	packet[5] = (unsigned char)(hp->h_addr_list[0])[1];
+	packet[6] = (unsigned char)(hp->h_addr_list[0])[2];
+	packet[7] = (unsigned char)(hp->h_addr_list[0])[3];
+	packet[8] = 0;
+	if (write(source, packet, 9) != 9) {
+		close(source);
+		phb->func(phb->data, -1, GDK_INPUT_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+
+	phb->inpa = gdk_input_add(source, GDK_INPUT_READ, s4_canread, phb);
 }
 
 static int proxy_connect_socks4(char *host, unsigned short port,
-				char *proxyhost, unsigned short proxyport)
+				char *proxyhost, unsigned short proxyport,
+				struct PHB *phb)
 {
 	struct sockaddr_in sin;
-	unsigned char packet[12];
 	struct hostent *hp;
 	int fd = -1;
 
 	debug_printf("connecting to %s:%d via %s:%d using SOCKS4\n", host, port, proxyhost, proxyport);
 
-	if (!(hp = gethostbyname(proxyhost)))
+	if (!(hp = gethostbyname(proxyhost))) {
+		g_free(phb);
 		return -1;
+	}
 
 	memset(&sin, 0, sizeof(struct sockaddr_in));
 	memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
 	sin.sin_family = hp->h_addrtype;
 	sin.sin_port = htons(proxyport);
 
-	if ((fd = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0)
-		return -1;
-
-	if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
-		close(fd);
+	if ((fd = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0) {
+		g_free(phb);
 		return -1;
 	}
 
-	/* XXX does socks4 not support host name lookups by the proxy? */
-	if (!(hp = gethostbyname(host)))
-		return -1;
+	phb->host = g_strdup(host);
+	phb->port = port;
+
+	fcntl(fd, F_SETFL, O_NONBLOCK);
+	if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+		if ((errno == EINPROGRESS) || (errno == EINTR)) {
+			debug_printf("Connect would have blocked\n");
+			phb->inpa = gdk_input_add(fd, GDK_INPUT_WRITE, s4_canwrite, phb);
+		} else {
+			close(fd);
+			g_free(phb->host);
+			g_free(phb);
+			return -1;
+		}
+	} else {
+		int len, error = ETIMEDOUT;
+		debug_printf("Connect didn't block\n");
+		len = sizeof(error);
+		if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
+			close(fd);
+			g_free(phb->host);
+			g_free(phb);
+			return -1;
+		}
+		fcntl(fd, F_SETFL, 0);
+		s4_canwrite(phb, fd, GDK_INPUT_WRITE);
+	}
 
-	packet[0] = 4;
-	packet[1] = 1;
-	packet[2] = port >> 8;
-	packet[3] = port & 0xff;
-	packet[4] = (unsigned char)(hp->h_addr_list[0])[0];
-	packet[5] = (unsigned char)(hp->h_addr_list[0])[1];
-	packet[6] = (unsigned char)(hp->h_addr_list[0])[2];
-	packet[7] = (unsigned char)(hp->h_addr_list[0])[3];
-	packet[8] = 0;
-	if (write(fd, packet, 9) == 9) {
-		memset(packet, 0, sizeof(packet));
-		if (read(fd, packet, 9) >= 4 && packet[1] == 90)
-			return fd;
+	return fd;
+}
+
+static void s5_canread_again(gpointer data, gint source, GdkInputCondition cond)
+{
+	unsigned char buf[512];
+	struct PHB *phb = data;
+
+	gdk_input_remove(phb->inpa);
+	debug_printf("able to read again\n");
+
+	if (read(source, buf, 10) < 10) {
+		debug_printf("or not...\n");
+		close(source);
+		phb->func(phb->data, -1, GDK_INPUT_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return;
 	}
-	close(fd);
+	if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
+		debug_printf("bad data\n");
+		close(source);
+		phb->func(phb->data, -1, GDK_INPUT_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
 
-	return -1;
+	phb->func(phb->data, source, GDK_INPUT_READ);
+	g_free(phb->host);
+	g_free(phb);
+	return;
 }
 
-static int proxy_connect_socks5(char *host, unsigned short port,
-				char *proxyhost, unsigned short proxyport)
+static void s5_canread(gpointer data, gint source, GdkInputCondition cond)
 {
-	int i, fd = -1;
 	unsigned char buf[512];
-	struct sockaddr_in sin;
-	struct hostent *hp;
-	int hlen = strlen(host);
+	struct PHB *phb = data;
+	int hlen = strlen(phb->host);
+
+	gdk_input_remove(phb->inpa);
+	debug_printf("able to read\n");
 
-	debug_printf("connecting to %s:%d via %s:%d using SOCKS5\n", host, port, proxyhost, proxyport);
+	if (read(source, buf, 2) < 2) {
+		close(source);
+		phb->func(phb->data, -1, GDK_INPUT_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+
+	if ((buf[0] != 0x05) || (buf[1] == 0xff)) {
+		close(source);
+		phb->func(phb->data, -1, GDK_INPUT_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
 
-	if (!(hp = gethostbyname(proxyhost)))
-		return -1;
+	buf[0] = 0x05;
+	buf[1] = 0x01;		/* CONNECT */
+	buf[2] = 0x00;		/* reserved */
+	buf[3] = 0x03;		/* address type -- host name */
+	buf[4] = hlen;
+	memcpy(buf + 5, phb->host, hlen);
+	buf[5 + strlen(phb->host)] = phb->port >> 8;
+	buf[5 + strlen(phb->host) + 1] = phb->port & 0xff;
 
-	memset(&sin, 0, sizeof(struct sockaddr_in));
-	memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
-	sin.sin_family = hp->h_addrtype;
-	sin.sin_port = htons(proxyport);
+	if (write(source, buf, (5 + strlen(phb->host) + 2)) < (5 + strlen(phb->host) + 2)) {
+		close(source);
+		phb->func(phb->data, -1, GDK_INPUT_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
 
-	if ((fd = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0)
-		return -1;
+	phb->inpa = gdk_input_add(source, GDK_INPUT_READ, s5_canread_again, phb);
+}
 
-	if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
-		close(fd);
-		return -1;
+static void s5_canwrite(gpointer data, gint source, GdkInputCondition cond)
+{
+	unsigned char buf[512];
+	int i;
+	struct PHB *phb = data;
+	int len, error = ETIMEDOUT;
+	debug_printf("Connected\n");
+	if (phb->inpa > 0)
+		gdk_input_remove(phb->inpa);
+	len = sizeof(error);
+	if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
+		close(source);
+		phb->func(phb->data, -1, GDK_INPUT_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return;
 	}
+	fcntl(source, F_SETFL, 0);
 
 	i = 0;
 	buf[0] = 0x05;		/* SOCKS version 5 */
@@ -202,58 +466,98 @@
 	buf[2] = 0x00;
 	i = 3;
 
-	if (write(fd, buf, i) < i) {
-		close(fd);
-		return -1;
+	if (write(source, buf, i) < i) {
+		debug_printf("unable to write\n");
+		close(source);
+		phb->func(phb->data, -1, GDK_INPUT_READ);
+		g_free(phb->host);
+		g_free(phb);
+		return;
 	}
 
-	if (read(fd, buf, 2) < 2) {
-		close(fd);
-		return -1;
-	}
+	phb->inpa = gdk_input_add(source, GDK_INPUT_READ, s5_canread, phb);
+}
 
-	if ((buf[0] != 0x05) || (buf[1] == 0xff)) {
-		close(fd);
+static int proxy_connect_socks5(char *host, unsigned short port,
+				char *proxyhost, unsigned short proxyport,
+				struct PHB *phb)
+{
+	int fd = -1;
+	struct sockaddr_in sin;
+	struct hostent *hp;
+
+	debug_printf("connecting to %s:%d via %s:%d using SOCKS5\n", host, port, proxyhost, proxyport);
+
+	if (!(hp = gethostbyname(proxyhost))) {
+		g_free(phb);
 		return -1;
 	}
 
-	buf[0] = 0x05;
-	buf[1] = 0x01;		/* CONNECT */
-	buf[2] = 0x00;		/* reserved */
-	buf[3] = 0x03;		/* address type -- host name */
-	buf[4] = hlen;
-	memcpy(buf + 5, host, hlen);
-	buf[5 + strlen(host)] = port >> 8;
-	buf[5 + strlen(host) + 1] = port & 0xff;
+	memset(&sin, 0, sizeof(struct sockaddr_in));
+	memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
+	sin.sin_family = hp->h_addrtype;
+	sin.sin_port = htons(proxyport);
 
-	if (write(fd, buf, (5 + strlen(host) + 2)) < (5 + strlen(host) + 2)) {
-		close(fd);
+	if ((fd = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0) {
+		g_free(phb);
 		return -1;
 	}
-	if (read(fd, buf, 10) < 10) {
-		close(fd);
-		return -1;
-	}
-	if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
-		close(fd);
-		return -1;
+
+	phb->host = g_strdup(host);
+	phb->port = port;
+
+	fcntl(fd, F_SETFL, O_NONBLOCK);
+	if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+		if ((errno == EINPROGRESS) || (errno == EINTR)) {
+			debug_printf("Connect would have blocked\n");
+			phb->inpa = gdk_input_add(fd, GDK_INPUT_WRITE, s5_canwrite, phb);
+		} else {
+			close(fd);
+			g_free(phb->host);
+			g_free(phb);
+			return -1;
+		}
+	} else {
+		int len, error = ETIMEDOUT;
+		debug_printf("Connect didn't block\n");
+		len = sizeof(error);
+		if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
+			close(fd);
+			g_free(phb->host);
+			g_free(phb);
+			return -1;
+		}
+		fcntl(fd, F_SETFL, 0);
+		s5_canwrite(phb, fd, GDK_INPUT_WRITE);
 	}
 
 	return fd;
 }
 
-int proxy_connect(char *host, int port, char *proxyhost, int proxyport, int proxytype)
+int proxy_connect(char *host, int port,
+		  char *proxyhost, int proxyport, int proxytype,
+		  GdkInputFunction func, gpointer data)
 {
-	if (!host || !port || (port == -1))
+	struct PHB *phb = g_new0(struct PHB, 1);
+	phb->func = func;
+	phb->data = data;
+
+	if (!host || !port || (port == -1) || !func) {
+		g_free(phb);
 		return -1;
-	else if ((proxytype == PROXY_NONE) ||
+	}
+
+	if ((proxytype == PROXY_NONE) ||
 		 !proxyhost || !proxyhost[0] ||
-		 !proxyport || (proxyport == -1)) return proxy_connect_none(host, port);
+		 !proxyport || (proxyport == -1))
+		return proxy_connect_none(host, port, phb);
 	else if (proxytype == PROXY_HTTP)
-		return proxy_connect_http(host, port, proxyhost, proxyport);
+		return proxy_connect_http(host, port, proxyhost, proxyport, phb);
 	else if (proxytype == PROXY_SOCKS4)
-		return proxy_connect_socks4(host, port, proxyhost, proxyport);
+		return proxy_connect_socks4(host, port, proxyhost, proxyport, phb);
 	else if (proxytype == PROXY_SOCKS5)
-		return proxy_connect_socks5(host, port, proxyhost, proxyport);
+		return proxy_connect_socks5(host, port, proxyhost, proxyport, phb);
+
+	g_free(phb);
 	return -1;
 }
--- a/src/proxy.h	Fri May 11 21:54:27 2001 +0000
+++ b/src/proxy.h	Sat May 12 01:38:04 2001 +0000
@@ -30,12 +30,15 @@
 #include <sys/socket.h>
 #include <netdb.h>
 #include <netinet/in.h>
+#include <gtk/gtk.h>
 
 #define PROXY_NONE 0
 #define PROXY_HTTP 1
 #define PROXY_SOCKS4 2
 #define PROXY_SOCKS5 3
 
-extern int proxy_connect(char *host, int port, char *proxyhost, int proxyport, int proxytype);
+extern int proxy_connect(char *host, int port,
+			 char *proxyhost, int proxyport, int proxytype,
+			 GdkInputFunction func, gpointer data);
 
 #endif
--- a/src/toc.c	Fri May 11 21:54:27 2001 +0000
+++ b/src/toc.c	Sat May 12 01:38:04 2001 +0000
@@ -115,13 +115,14 @@
 /* constants to identify proto_opts */
 #define USEROPT_AUTH      0
 #define USEROPT_AUTHPORT  1
-#define USEROPT_SOCKSHOST 2
-#define USEROPT_SOCKSPORT 3
+#define USEROPT_PROXYHOST 2
+#define USEROPT_PROXYPORT 3
 #define USEROPT_PROXYTYPE 4
 
 static GtkWidget *join_chat_spin = NULL;
 static GtkWidget *join_chat_entry = NULL;
 
+static void toc_login_callback(gpointer, gint, GdkInputCondition);
 static void toc_callback(gpointer, gint, GdkInputCondition);
 static unsigned char *roast_password(char *);
 static void accept_file_dialog(struct ft_request *);
@@ -140,28 +141,41 @@
 
 	g_snprintf(buf, sizeof buf, "Looking up %s",
 		   user->proto_opt[USEROPT_AUTH][0] ? user->proto_opt[USEROPT_AUTH] : TOC_HOST);
-	/* this is such a hack */
 	set_login_progress(gc, 1, buf);
-	while (gtk_events_pending())
-		gtk_main_iteration();
-	if (!g_slist_find(connections, gc))
-		return;
 
+	debug_printf("* Client connects to TOC\n");
 	tdt->toc_fd =
 	    proxy_connect(user->proto_opt[USEROPT_AUTH][0] ? user->proto_opt[USEROPT_AUTH] : TOC_HOST,
-			  user->proto_opt[USEROPT_AUTHPORT][0] ? atoi(user->
-								      proto_opt[USEROPT_AUTHPORT]) :
-			  TOC_PORT, user->proto_opt[USEROPT_SOCKSHOST],
-			  atoi(user->proto_opt[USEROPT_SOCKSPORT]),
-			  atoi(user->proto_opt[USEROPT_PROXYTYPE]));
+			  user->proto_opt[USEROPT_AUTHPORT][0] ?
+				  atoi(user->proto_opt[USEROPT_AUTHPORT]) : TOC_PORT,
+			  user->proto_opt[USEROPT_PROXYHOST],
+			  atoi(user->proto_opt[USEROPT_PROXYPORT]),
+			  atoi(user->proto_opt[USEROPT_PROXYTYPE]),
+			  toc_login_callback, gc);
 
-	debug_printf("* Client connects to TOC\n");
 	if (tdt->toc_fd < 0) {
 		g_snprintf(buf, sizeof(buf), "Connect to %s failed", user->proto_opt[USEROPT_AUTH]);
 		hide_login_progress(gc, buf);
 		signoff(gc);
 		return;
 	}
+}
+
+static void toc_login_callback(gpointer data, gint source, GdkInputCondition cond)
+{
+	struct gaim_connection *gc = data;
+	struct toc_data *tdt = gc->proto_data;
+	char buf[80];
+
+	if (source == -1) {
+		/* we didn't successfully connect. tdt->toc_fd is valid here */
+		hide_login_progress(gc, "Unable to connect.");
+		signoff(gc);
+		return;
+	}
+
+	if (tdt->toc_fd == 0)
+		tdt->toc_fd = source;
 
 	debug_printf("* Client sends \"FLAPON\\r\\n\\r\\n\"\n");
 	if (write(tdt->toc_fd, FLAPON, strlen(FLAPON)) < 0) {
@@ -280,6 +294,14 @@
 	return rp;
 }
 
+static void toc_got_info(gpointer data, char *url_text)
+{
+	if (!url_text)
+		return;
+
+	g_show_info_text(url_text);
+}
+
 static void toc_callback(gpointer data, gint source, GdkInputCondition condition)
 {
 	struct gaim_connection *gc = (struct gaim_connection *)data;
@@ -557,8 +579,13 @@
 		name = strtok(NULL, ":");
 		url = strtok(NULL, ":");
 
-		g_snprintf(tmp, sizeof(tmp), "http://%s:%d/%s", TOC_HOST, TOC_PORT, url);
-		g_show_info(gc->user, tmp);
+		g_snprintf(tmp, sizeof(tmp), "http://%s:%d/%s",
+				gc->user->proto_opt[USEROPT_AUTH][0] ?
+					gc->user->proto_opt[USEROPT_AUTH] : TOC_HOST,
+				gc->user->proto_opt[USEROPT_AUTHPORT][0] ?
+					atoi(gc->user->proto_opt[USEROPT_AUTHPORT]) : TOC_PORT,
+				url);
+		grab_url(gc->user, tmp, toc_got_info, NULL);
 	} else if (!strcasecmp(c, "DIR_STATUS")) {
 	} else if (!strcasecmp(c, "ADMIN_NICK_STATUS")) {
 	} else if (!strcasecmp(c, "ADMIN_PASSWD_STATUS")) {
@@ -993,12 +1020,12 @@
 	} else if (entrynum == USEROPT_AUTHPORT) {
 		g_snprintf(user->proto_opt[USEROPT_AUTHPORT],
 			   sizeof(user->proto_opt[USEROPT_AUTHPORT]), "%s", gtk_entry_get_text(entry));
-	} else if (entrynum == USEROPT_SOCKSHOST) {
-		g_snprintf(user->proto_opt[USEROPT_SOCKSHOST],
-			   sizeof(user->proto_opt[USEROPT_SOCKSHOST]), "%s", gtk_entry_get_text(entry));
-	} else if (entrynum == USEROPT_SOCKSPORT) {
-		g_snprintf(user->proto_opt[USEROPT_SOCKSPORT],
-			   sizeof(user->proto_opt[USEROPT_SOCKSPORT]), "%s", gtk_entry_get_text(entry));
+	} else if (entrynum == USEROPT_PROXYHOST) {
+		g_snprintf(user->proto_opt[USEROPT_PROXYHOST],
+			   sizeof(user->proto_opt[USEROPT_PROXYHOST]), "%s", gtk_entry_get_text(entry));
+	} else if (entrynum == USEROPT_PROXYPORT) {
+		g_snprintf(user->proto_opt[USEROPT_PROXYPORT],
+			   sizeof(user->proto_opt[USEROPT_PROXYPORT]), "%s", gtk_entry_get_text(entry));
 	}
 }
 
@@ -1078,11 +1105,11 @@
 
 	entry = gtk_entry_new();
 	gtk_box_pack_end(GTK_BOX(hbox), entry, FALSE, FALSE, 0);
-	gtk_object_set_user_data(GTK_OBJECT(entry), (void *)USEROPT_SOCKSHOST);
+	gtk_object_set_user_data(GTK_OBJECT(entry), (void *)USEROPT_PROXYHOST);
 	gtk_signal_connect(GTK_OBJECT(entry), "changed", GTK_SIGNAL_FUNC(toc_print_option), user);
-	if (user->proto_opt[USEROPT_SOCKSHOST][0]) {
-		debug_printf("setting text %s\n", user->proto_opt[USEROPT_SOCKSHOST]);
-		gtk_entry_set_text(GTK_ENTRY(entry), user->proto_opt[USEROPT_SOCKSHOST]);
+	if (user->proto_opt[USEROPT_PROXYHOST][0]) {
+		debug_printf("setting text %s\n", user->proto_opt[USEROPT_PROXYHOST]);
+		gtk_entry_set_text(GTK_ENTRY(entry), user->proto_opt[USEROPT_PROXYHOST]);
 	}
 	gtk_widget_show(entry);
 
@@ -1097,11 +1124,11 @@
 
 	entry = gtk_entry_new();
 	gtk_box_pack_end(GTK_BOX(hbox), entry, FALSE, FALSE, 0);
-	gtk_object_set_user_data(GTK_OBJECT(entry), (void *)USEROPT_SOCKSPORT);
+	gtk_object_set_user_data(GTK_OBJECT(entry), (void *)USEROPT_PROXYPORT);
 	gtk_signal_connect(GTK_OBJECT(entry), "changed", GTK_SIGNAL_FUNC(toc_print_option), user);
-	if (user->proto_opt[USEROPT_SOCKSPORT][0]) {
-		debug_printf("setting text %s\n", user->proto_opt[USEROPT_SOCKSPORT]);
-		gtk_entry_set_text(GTK_ENTRY(entry), user->proto_opt[USEROPT_SOCKSPORT]);
+	if (user->proto_opt[USEROPT_PROXYPORT][0]) {
+		debug_printf("setting text %s\n", user->proto_opt[USEROPT_PROXYPORT]);
+		gtk_entry_set_text(GTK_ENTRY(entry), user->proto_opt[USEROPT_PROXYPORT]);
 	}
 	gtk_widget_show(entry);
 
@@ -1375,6 +1402,7 @@
 	char *ip;
 	int port;
 	long size;
+	struct stat st;
 
 	GtkWidget *window;
 	int files;
@@ -1521,6 +1549,23 @@
 	}
 }
 
+static void toc_send_file_connect(gpointer data, gint src, GdkInputCondition cond)
+{
+	struct file_transfer *ft = data;
+
+	if (src == -1) {
+		do_error_dialog(_("Could not connect for transfer!"), _("Error"));
+		g_free(ft->filename);
+		g_free(ft->cookie);
+		g_free(ft->user);
+		g_free(ft->ip);
+		g_free(ft);
+		return;
+	}
+
+	ft->inpa = gdk_input_add(src, GDK_INPUT_READ | GDK_INPUT_EXCEPTION, toc_send_file_callback, ft);
+}
+
 static void toc_send_file(gpointer a, struct file_transfer *old_ft)
 {
 	struct file_transfer *ft;
@@ -1550,9 +1595,10 @@
 
 	fd =
 	    proxy_connect(ft->ip, ft->port,
-			  user->proto_opt[USEROPT_SOCKSHOST],
-			  atoi(user->proto_opt[USEROPT_SOCKSPORT]),
-			  atoi(user->proto_opt[USEROPT_PROXYTYPE]));
+			  user->proto_opt[USEROPT_PROXYHOST],
+			  atoi(user->proto_opt[USEROPT_PROXYPORT]),
+			  atoi(user->proto_opt[USEROPT_PROXYTYPE]),
+			  toc_send_file_connect, ft);
 	if (fd < 0) {
 		do_error_dialog(_("Could not connect for transfer!"), _("Error"));
 		g_free(ft->filename);
@@ -1562,8 +1608,6 @@
 		g_free(ft);
 		return;
 	}
-
-	ft->inpa = gdk_input_add(fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION, toc_send_file_callback, ft);
 }
 
 static void toc_get_file_callback(gpointer data, gint source, GdkInputCondition cond)
@@ -1683,15 +1727,63 @@
 	}
 }
 
+static void toc_get_file_connect(gpointer data, gint src, GdkInputCondition cond)
+{
+	struct file_transfer *ft = data;
+	struct file_header *hdr;
+	char *buf;
+
+	if (src == -1) {
+		do_error_dialog(_("Could not connect for transfer!"), _("Error"));
+		fclose(ft->file);
+		g_free(ft->filename);
+		g_free(ft->cookie);
+		g_free(ft->user);
+		g_free(ft->ip);
+		g_free(ft);
+		return;
+	}
+
+	hdr = (struct file_header *)ft;
+	hdr->magic[0] = 'O'; hdr->magic[1] = 'F'; hdr->magic[2] = 'T'; hdr->magic[3] = '2';
+	hdr->hdrlen = htons(256);
+	hdr->hdrtype = htons(0x1108);
+	buf = frombase64(ft->cookie);
+	g_snprintf(hdr->bcookie, 8, "%s", buf);
+	g_free(buf);
+	hdr->totfiles = htons(1); hdr->filesleft = htons(1);
+	hdr->totparts = htons(1); hdr->partsleft = htons(1);
+	hdr->totsize = htonl((long)ft->st.st_size); /* combined size of all files */
+	/* size = strlen("mm/dd/yyyy hh:mm sizesize 'name'\r\n") */
+	hdr->size = htonl(28 + strlen(g_basename(ft->filename))); /* size of listing.txt */
+	hdr->modtime = htonl(ft->st.st_mtime);
+	hdr->checksum = htonl(0x89f70000); /* uh... */
+	g_snprintf(hdr->idstring, 32, "OFT_Windows ICBMFT V1.1 32");
+	hdr->flags = 0x02;
+	hdr->lnameoffset = 0x1A;
+	hdr->lsizeoffset = 0x10;
+	g_snprintf(hdr->name, 64, "listing.txt");
+	if (write(src, hdr, 256) < 0) {
+		do_error_dialog(_("Could not write file header!"), _("Error"));
+		fclose(ft->file);
+		g_free(ft->filename);
+		g_free(ft->cookie);
+		g_free(ft->user);
+		g_free(ft->ip);
+		g_free(ft);
+		return;
+	}
+
+	ft->inpa = gdk_input_add(src, GDK_INPUT_READ | GDK_INPUT_EXCEPTION, toc_get_file_callback, ft);
+}
+
 static void toc_get_file(gpointer a, struct file_transfer *old_ft)
 {
 	struct file_transfer *ft;
-	struct file_header *hdr;
 	char *dirname = gtk_file_selection_get_filename(GTK_FILE_SELECTION(old_ft->window));
 	int fd;
 	struct aim_user *user;
 	char *buf, buf2[BUF_LEN * 2];
-	struct stat st;
 
 	if (file_is_dir(dirname, old_ft->window))
 		return;
@@ -1706,7 +1798,7 @@
 		g_free(ft);
 		return;
 	}
-	if (stat(dirname, &st)) {
+	if (stat(dirname, &ft->st)) {
 		buf = g_strdup_printf("Unable to examine %s!", dirname);
 		do_error_dialog(buf, "Error");
 		g_free(buf);
@@ -1727,9 +1819,10 @@
 
 	fd =
 	    proxy_connect(ft->ip, ft->port,
-			  user->proto_opt[USEROPT_SOCKSHOST],
-			  atoi(user->proto_opt[USEROPT_SOCKSPORT]),
-			  atoi(user->proto_opt[USEROPT_PROXYTYPE]));
+			  user->proto_opt[USEROPT_PROXYHOST],
+			  atoi(user->proto_opt[USEROPT_PROXYPORT]),
+			  atoi(user->proto_opt[USEROPT_PROXYTYPE]),
+			  toc_get_file_connect, ft);
 	if (fd < 0) {
 		do_error_dialog(_("Could not connect for transfer!"), _("Error"));
 		fclose(ft->file);
@@ -1740,38 +1833,6 @@
 		g_free(ft);
 		return;
 	}
-
-	hdr = (struct file_header *)ft;
-	hdr->magic[0] = 'O'; hdr->magic[1] = 'F'; hdr->magic[2] = 'T'; hdr->magic[3] = '2';
-	hdr->hdrlen = htons(256);
-	hdr->hdrtype = htons(0x1108);
-	buf = frombase64(ft->cookie);
-	g_snprintf(hdr->bcookie, 8, "%s", buf);
-	g_free(buf);
-	hdr->totfiles = htons(1); hdr->filesleft = htons(1);
-	hdr->totparts = htons(1); hdr->partsleft = htons(1);
-	hdr->totsize = htonl((long)st.st_size); /* combined size of all files */
-	/* size = strlen("mm/dd/yyyy hh:mm sizesize 'name'\r\n") */
-	hdr->size = htonl(28 + strlen(g_basename(ft->filename))); /* size of listing.txt */
-	hdr->modtime = htonl(st.st_mtime);
-	hdr->checksum = htonl(0x89f70000); /* uh... */
-	g_snprintf(hdr->idstring, 32, "OFT_Windows ICBMFT V1.1 32");
-	hdr->flags = 0x02;
-	hdr->lnameoffset = 0x1A;
-	hdr->lsizeoffset = 0x10;
-	g_snprintf(hdr->name, 64, "listing.txt");
-	if (write(fd, hdr, 256) < 0) {
-		do_error_dialog(_("Could not write file header!"), _("Error"));
-		fclose(ft->file);
-		g_free(ft->filename);
-		g_free(ft->cookie);
-		g_free(ft->user);
-		g_free(ft->ip);
-		g_free(ft);
-		return;
-	}
-
-	ft->inpa = gdk_input_add(fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION, toc_get_file_callback, ft);
 }
 
 static void cancel_callback(gpointer a, struct file_transfer *ft) {