changeset 7134:67f9b43c402a

[gaim-migrate @ 7701] I think this is the fifth Yahoo authentication method Gaim's seen in its days. Please tell me if anything stops working. committer: Tailor Script <tailor@pidgin.im>
author Sean Egan <seanegan@gmail.com>
date Fri, 03 Oct 2003 23:01:13 +0000
parents 28dd20b5f4cf
children eba5f7be0bc8
files src/protocols/msn/msn.c src/protocols/msn/notification.c src/protocols/msn/utils.c src/protocols/msn/utils.h src/protocols/yahoo/yahoo.c src/protocols/yahoo/yahoo.h src/util.c src/util.h
diffstat 8 files changed, 311 insertions(+), 118 deletions(-) [+]
line wrap: on
line diff
--- a/src/protocols/msn/msn.c	Fri Oct 03 21:57:44 2003 +0000
+++ b/src/protocols/msn/msn.c	Fri Oct 03 23:01:13 2003 +0000
@@ -72,7 +72,7 @@
 	}
 
 	g_snprintf(outparams, sizeof(outparams), "%s %s",
-			   gaim_account_get_username(account), msn_url_encode(alias));
+			   gaim_account_get_username(account), gaim_url_encode(alias));
 
 	g_free(alias);
 
@@ -94,7 +94,7 @@
 		g_snprintf(outparams, sizeof(outparams), "%s  ", type);
 	else
 		g_snprintf(outparams, sizeof(outparams), "%s %s", type,
-				   msn_url_encode(entry));
+				   gaim_url_encode(entry));
 
 	if (!msn_servconn_send_command(session->notification_conn,
 								   "PRP", outparams)) {
@@ -1066,7 +1066,7 @@
 
 	if (new_group == NULL) {
 		g_snprintf(outparams, sizeof(outparams), "%s 0",
-				   msn_url_encode(new_group_name));
+				   gaim_url_encode(new_group_name));
 
 		if (!msn_servconn_send_command(session->notification_conn,
 									   "ADG", outparams)) {
@@ -1086,7 +1086,7 @@
 	}
 	else {
 		g_snprintf(outparams, sizeof(outparams), "FL %s %s %d",
-				   who, msn_url_encode(friendly),
+				   who, gaim_url_encode(friendly),
 				   msn_group_get_id(new_group));
 
 		if (!msn_servconn_send_command(session->notification_conn,
@@ -1133,7 +1133,7 @@
 
 		g_snprintf(outparams, sizeof(outparams), "%d %s 0",
 				   msn_group_get_id(old_group),
-				   msn_url_encode(new_group_name));
+				   gaim_url_encode(new_group_name));
 
 		if (!msn_servconn_send_command(session->notification_conn,
 									   "REG", outparams)) {
@@ -1145,7 +1145,7 @@
 	}
 	else {
 		g_snprintf(outparams, sizeof(outparams), "%s 0",
-				   msn_url_encode(new_group_name));
+				   gaim_url_encode(new_group_name));
 
 		if (!msn_servconn_send_command(session->notification_conn,
 									   "ADG", outparams)) {
--- a/src/protocols/msn/notification.c	Fri Oct 03 21:57:44 2003 +0000
+++ b/src/protocols/msn/notification.c	Fri Oct 03 23:01:13 2003 +0000
@@ -172,7 +172,7 @@
 
 		g_snprintf(outparams, sizeof(outparams), "AL %s %s",
 				   msn_user_get_passport(pa->user),
-				   msn_url_encode(msn_user_get_name(pa->user)));
+				   gaim_url_encode(msn_user_get_name(pa->user)));
 
 		if (msn_servconn_send_command(session->notification_conn,
 									  "ADD", outparams) <= 0) {
@@ -200,7 +200,7 @@
 
 		g_snprintf(outparams, sizeof(outparams), "BL %s %s",
 				   msn_user_get_passport(pa->user),
-				   msn_url_encode(msn_user_get_name(pa->user)));
+				   gaim_url_encode(msn_user_get_name(pa->user)));
 
 		if (msn_servconn_send_command(session->notification_conn,
 									  "ADD", outparams) <= 0) {
@@ -325,9 +325,9 @@
 	size_t s;
 
 	username =
-		g_strdup(msn_url_encode(gaim_account_get_username(session->account)));
+		g_strdup(gaim_url_encode(gaim_account_get_username(session->account)));
 	password =
-		g_strdup(msn_url_encode(gaim_account_get_password(session->account)));
+		g_strdup(gaim_url_encode(gaim_account_get_password(session->account)));
 
 	request_str = g_strdup_printf(
 		"GET %s HTTP/1.1\r\n"
@@ -428,7 +428,7 @@
 			if ((error = strstr(buffer, "cbtxt=")) != NULL)
 				error += strlen("cbtxt=");
 
-			error = msn_url_decode(error);
+			error = gaim_url_decode(error);
 		}
 
 
@@ -600,7 +600,7 @@
 	 */
 	if (!g_ascii_strcasecmp(params[1], "OK"))
 	{
-		const char *friendly = msn_url_decode(params[3]);
+		const char *friendly = gaim_url_decode(params[3]);
 
 		/* OK */
 
@@ -658,7 +658,7 @@
 		g_free(challenge_data);
 
 #if 0
-		passport_str = g_strdup(msn_url_decode(params[3]));
+		passport_str = g_strdup(gaim_url_decode(params[3]));
 
 		for (c = passport_str; *c != '\0'; c++)
 		{
@@ -888,7 +888,7 @@
 
 	list     = params[1];
 	passport = params[3];
-	friend   = msn_url_decode(params[4]);
+	friend   = gaim_url_decode(params[4]);
 
 	if (param_count >= 6)
 		group_id = params[5];
@@ -939,7 +939,7 @@
 
 	group_id = atoi(params[3]);
 
-	group_name = msn_url_decode(params[2]);
+	group_name = gaim_url_decode(params[2]);
 
 	group = msn_group_new(session, group_id, group_name);
 
@@ -1022,11 +1022,11 @@
 			}
 		}
 		else if (!strcmp(type, "PHH"))
-			msn_user_set_home_phone(user, msn_url_decode(value));
+			msn_user_set_home_phone(user, gaim_url_decode(value));
 		else if (!strcmp(type, "PHW"))
-			msn_user_set_work_phone(user, msn_url_decode(value));
+			msn_user_set_work_phone(user, gaim_url_decode(value));
 		else if (!strcmp(type, "PHM"))
-			msn_user_set_mobile_phone(user, msn_url_decode(value));
+			msn_user_set_mobile_phone(user, gaim_url_decode(value));
 	}
 
 	return TRUE;
@@ -1057,7 +1057,7 @@
 
 	state    = params[1];
 	passport = params[2];
-	friend   = msn_url_decode(params[3]);
+	friend   = gaim_url_decode(params[3]);
 
 	user = msn_users_find_with_passport(session->users, passport);
 
@@ -1067,7 +1067,7 @@
 
 	if (session->protocol_ver >= 9 && param_count == 6)
 	{
-		msnobj = msn_object_new_from_string(msn_url_decode(params[5]));
+		msnobj = msn_object_new_from_string(gaim_url_decode(params[5]));
 		msn_user_set_object(user, msnobj);
 	}
 
@@ -1121,13 +1121,13 @@
 	if (session->protocol_ver >= 8)
 	{
 		group_id = atoi(params[0]);
-		name = msn_url_decode(params[1]);
+		name = gaim_url_decode(params[1]);
 	}
 	else
 	{
 		num_groups = atoi(params[3]);
 		group_id   = atoi(params[4]);
-		name       = msn_url_decode(params[5]);
+		name       = gaim_url_decode(params[5]);
 
 		if (num_groups == 0)
 			return TRUE;
@@ -1164,7 +1164,7 @@
 		int list_op;
 
 		passport   = params[0];
-		friend     = msn_url_decode(params[1]);
+		friend     = gaim_url_decode(params[1]);
 		list_op    = atoi(params[2]);
 		group_nums = params[3];
 
@@ -1332,7 +1332,7 @@
 		if (num_users > 0)
 		{
 			passport  = params[5];
-			friend    = msn_url_decode(params[6]);
+			friend    = gaim_url_decode(params[6]);
 		}
 
 		if (session->syncing_lists && session->lists_synced)
@@ -1478,7 +1478,7 @@
 
 	state    = params[0];
 	passport = params[1];
-	friend   = msn_url_decode(params[2]);
+	friend   = gaim_url_decode(params[2]);
 
 	user = msn_users_find_with_passport(session->users, passport);
 
@@ -1488,7 +1488,7 @@
 
 	if (session->protocol_ver >= 9 && param_count == 5)
 	{
-		msnobj = msn_object_new_from_string(msn_url_decode(params[4]));
+		msnobj = msn_object_new_from_string(gaim_url_decode(params[4]));
 		msn_user_set_object(user, msnobj);
 	}
 
@@ -1538,11 +1538,11 @@
 
 	if (param_count == 4) {
 		if (!strcmp(type, "PHH"))
-			msn_user_set_home_phone(session->user, msn_url_decode(value));
+			msn_user_set_home_phone(session->user, gaim_url_decode(value));
 		else if (!strcmp(type, "PHW"))
-			msn_user_set_work_phone(session->user, msn_url_decode(value));
+			msn_user_set_work_phone(session->user, gaim_url_decode(value));
 		else if (!strcmp(type, "PHM"))
-			msn_user_set_mobile_phone(session->user, msn_url_decode(value));
+			msn_user_set_mobile_phone(session->user, gaim_url_decode(value));
 	}
 
 	return TRUE;
@@ -1556,7 +1556,7 @@
 	GaimConnection *gc = session->account->gc;
 	char *friend;
 
-	friend = msn_url_decode(params[3]);
+	friend = gaim_url_decode(params[3]);
 
 	gaim_connection_set_display_name(gc, friend);
 
@@ -1574,7 +1574,7 @@
 
 	group_id = atoi(params[2]);
 
-	group_name = msn_url_decode(params[3]);
+	group_name = gaim_url_decode(params[3]);
 
 	group = msn_groups_find_with_id(session->groups, group_id);
 
@@ -1637,7 +1637,7 @@
 			friendly = passport;
 
 		g_snprintf(outparams, sizeof(outparams), "FL %s %s %d",
-				   passport, msn_url_encode(friendly),
+				   passport, gaim_url_encode(friendly),
 				   msn_group_get_id(group));
 
 		if (!msn_servconn_send_command(session->notification_conn,
--- a/src/protocols/msn/utils.c	Fri Oct 03 21:57:44 2003 +0000
+++ b/src/protocols/msn/utils.c	Fri Oct 03 23:01:13 2003 +0000
@@ -21,65 +21,6 @@
  */
 #include "msn.h"
 
-char *
-msn_url_decode(const char *str)
-{
-	static char buf[MSN_BUF_LEN];
-	int i, j = 0;
-	char *bum;
-
-	g_return_val_if_fail(str != NULL, NULL);
-
-	for (i = 0; i < strlen(str); i++) {
-		char hex[3];
-
-		if (str[i] != '%')
-			buf[j++] = str[i];
-		else {
-			strncpy(hex, str + ++i, 2);
-			hex[2] = '\0';
-
-			/* i is pointing to the start of the number */
-			i++;
-
-			/*
-			 * Now it's at the end and at the start of the for loop
-			 * will be at the next character.
-			 */
-			buf[j++] = strtol(hex, NULL, 16);
-		}
-	}
-
-	buf[j] = '\0';
-
-	if (!g_utf8_validate(buf, -1, (const char **)&bum))
-		*bum = '\0';
-
-	return buf;
-}
-
-char *
-msn_url_encode(const char *str)
-{
-	static char buf[MSN_BUF_LEN];
-	int i, j = 0;
-
-	g_return_val_if_fail(str != NULL, NULL);
-
-	for (i = 0; i < strlen(str); i++) {
-		if (isalnum(str[i]))
-			buf[j++] = str[i];
-		else {
-			sprintf(buf + j, "%%%02x", (unsigned char)str[i]);
-			j += 3;
-		}
-	}
-
-	buf[j] = '\0';
-
-	return buf;
-}
-
 void
 msn_parse_format(const char *mime, char **pre_ret, char **post_ret)
 {
@@ -146,7 +87,7 @@
 		}
 	}
 
-	cur = g_strdup(msn_url_decode(pre->str));
+	cur = g_strdup(gaim_url_decode(pre->str));
 	g_string_free(pre, TRUE);
 
 	if (pre_ret != NULL)
@@ -154,7 +95,7 @@
 	else
 		g_free(cur);
 
-	cur = g_strdup(msn_url_decode(post->str));
+	cur = g_strdup(gaim_url_decode(post->str));
 	g_string_free(post, TRUE);
 
 	if (post_ret != NULL)
--- a/src/protocols/msn/utils.h	Fri Oct 03 21:57:44 2003 +0000
+++ b/src/protocols/msn/utils.h	Fri Oct 03 23:01:13 2003 +0000
@@ -23,28 +23,6 @@
 #define _MSN_UTILS_H_
 
 /**
- * Decodes a URL into a plain string.
- *
- * This will change hex codes and such to their ascii equivalents.
- *
- * @param str The string to translate.
- *
- * @return The resulting string.
- */
-char *msn_url_decode(const char *str);
-
-/**
- * Encodes a URL into an escaped string.
- *
- * This will change non-alphanumeric characters to hex codes.
- *
- * @param str The string to translate.
- *
- * @return The resulting string.
- */
-char *msn_url_encode(const char *str);
-
-/**
  * Parses the MSN message formatting into a format compatible with Gaim.
  *
  * @param mime     The mime header with the formatting.
--- a/src/protocols/yahoo/yahoo.c	Fri Oct 03 21:57:44 2003 +0000
+++ b/src/protocols/yahoo/yahoo.c	Fri Oct 03 23:01:13 2003 +0000
@@ -39,6 +39,8 @@
 #include "yahoochat.h"
 #include "md5.h"
 
+#define YAHOO_WEBMESSENGER
+
 extern char *yahoo_crypt(const char *, const char *);
 
 typedef struct
@@ -56,7 +58,11 @@
 #define YAHOO_PAGER_PORT 5050
 #define YAHOO_PROFILE_URL "http://profiles.yahoo.com/"
 
+#ifdef YAHOO_WEBMESSENGER
+#define YAHOO_PROTO_VER 0x0065
+#else
 #define YAHOO_PROTO_VER 0x000b
+#endif
 
 #define YAHOO_PACKET_HDRLEN (4 + 2 + 2 + 2 + 2 + 4 + 4)
 
@@ -1490,7 +1496,6 @@
 	default:
 		msg = _("Unknown error.");
 	}
-
 	gaim_connection_error(gc, msg);
 }
 
@@ -1725,6 +1730,188 @@
 	gc->inpa = gaim_input_add(yd->fd, GAIM_INPUT_READ, yahoo_pending, gc);
 }
 
+#ifdef YAHOO_WEBMESSENGER
+static void yahoo_got_web_connected(gpointer data, gint source, GaimInputCondition cond)
+{
+	GaimConnection *gc = data;
+	struct yahoo_data *yd;
+	struct yahoo_packet *pkt;
+
+	if (!g_list_find(gaim_connections_get_all(), gc)) {
+		close(source);
+		return;
+	}
+
+	if (source < 0) {
+		gaim_connection_error(gc, _("Unable to connect"));
+		return;
+	}
+
+	yd = gc->proto_data;
+	yd->fd = source;
+
+	pkt = yahoo_packet_new(YAHOO_SERVICE_WEBLOGIN, YAHOO_STATUS_WEBLOGIN, 0);
+
+	yahoo_packet_hash(pkt, 0, gaim_normalize(gaim_account_get_username(gaim_connection_get_account(gc))));
+	yahoo_packet_hash(pkt, 1, gaim_normalize(gaim_account_get_username(gaim_connection_get_account(gc))));
+	yahoo_packet_hash(pkt, 6, yd->auth);
+	yahoo_send_packet(yd, pkt);
+
+	yahoo_packet_free(pkt);
+	g_free(yd->auth);
+	gc->inpa = gaim_input_add(yd->fd, GAIM_INPUT_READ, yahoo_pending, gc);
+}
+
+static void yahoo_web_pending(gpointer data, gint source, GaimInputCondition cond)
+{
+	GaimConnection *gc = data;
+	GaimAccount *account = gaim_connection_get_account(gc);
+	struct yahoo_data *yd = gc->proto_data;
+	char buf[1024], buf2[256], *i = buf, *r = buf2;
+	int len, o = 0;
+
+	len = read(source, buf, sizeof(buf));
+
+	if (len <= 0  || strncmp(buf, "HTTP/1.0 302", strlen("HTTP/1.0 302"))) {
+		gaim_connection_error(gc, _("Unable to read"));
+		return;
+	}
+	
+	while ((i = strstr(i, "Set-Cookie: ")) && 0 < 2) {
+		i += strlen("Set-Cookie: "); 
+		for (;*i != ';'; r++, i++) {
+			*r = *i;
+		}
+		*r=';';
+		r++;
+		*r=' ';
+		r++;
+		o++;
+	}
+	/* Get rid of that "; " */
+	*(r-2) = '\0';
+	yd->auth = g_strdup(buf2);
+	gaim_input_remove(gc->inpa);
+	close(source);
+
+	/* Now we have our cookies to login with.  I'll go get the milk. */
+	if (gaim_proxy_connect(account, "wcs1.msg.sc5.yahoo.com",
+			       gaim_account_get_int(account, "port", YAHOO_PAGER_PORT),
+			       yahoo_got_web_connected, gc) != 0) {
+		gaim_connection_error(gc, _("Connection problem"));
+		return;
+	}	
+}
+
+static void yahoo_got_cookies(gpointer data, gint source, GaimInputCondition cond)
+{
+	GaimConnection *gc = data;
+	struct yahoo_data *yd = gc->proto_data;
+	if (source < 0) {
+		gaim_connection_error(gc, _("Unable to connect"));
+		return;
+	}
+	write(source, yd->auth, strlen(yd->auth));
+	g_free(yd->auth);
+	gc->inpa = gaim_input_add(source, GAIM_INPUT_READ, yahoo_web_pending, gc);
+}
+
+static void yahoo_login_page_hash_iter(const char *key, const char *val, GString *url)
+{
+	if (!strcmp(key, "passwd"))
+		return;
+	url = g_string_append_c(url, '&');
+	url = g_string_append(url, key);
+	url = g_string_append_c(url, '=');
+	if (!strcmp(key, ".save") || !strcmp(key, ".js"))
+		url = g_string_append_c(url, '1');
+	else if (!strcmp(key, ".challenge"))
+		url = g_string_append(url, val);
+	else
+		url = g_string_append(url, gaim_url_encode(val));
+}
+
+static GHashTable *yahoo_login_page_hash(const char *buf, size_t len)
+{
+	GHashTable *hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+	const char *c = buf, *d;
+	char name[64], value[64];
+	while ((c < (buf + len)) && (c = strstr(c, "<input "))) {
+		c = strstr(c, "name=\"") + strlen("name=\"");
+		for (d = name; *c!='"'; c++, d++) 
+			*d = *c;
+		*d = '\0';
+		d = strstr(c, "value=\"") + strlen("value=\"");
+		if (strchr(c, '>') < d)
+			break;
+		for (c = d, d = value; *c!='"'; c++, d++)
+			*d = *c;
+		*d = '\0';
+		g_hash_table_insert(hash, g_strdup(name), g_strdup(value));
+	}
+	return hash;
+}
+
+static void yahoo_login_page_cb(GaimConnection *gc, const char *buf, size_t len)
+{
+	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_account_get_password(account);
+
+	GHashTable *hash = yahoo_login_page_hash(buf, len);
+	GString *url = g_string_new("GET /config/login?login=");
+	url = g_string_append(url, sn);
+	url = g_string_append(url, "&passwd=");
+	
+	char md5[33], *hashp = md5, *chal;
+	int i;
+	md5_byte_t result[16];
+	md5_state_t ctx;
+	md5_init(&ctx);
+	md5_append(&ctx, pass, strlen(pass));
+	md5_finish(&ctx, result);
+	for (i = 0; i < 16; ++i) {
+		g_snprintf(hashp, 3, "%02x", result[i]);
+		hashp += 2;
+	}
+	chal = g_strconcat(md5, g_hash_table_lookup(hash, ".challenge"), NULL);
+	md5_init(&ctx);
+	md5_append(&ctx, chal, strlen(chal));
+	md5_finish(&ctx, result);
+	hashp = md5;
+	for (i = 0; i < 16; ++i) {
+		g_snprintf(hashp, 3, "%02x", result[i]);
+		hashp += 2;
+	}
+	/*
+	md5_init(&ctx);
+	md5_append(&ctx, md5, strlen(md5));
+	md5_finish(&ctx, result);
+	hashp = md5;
+	for (i = 0; i < 16; ++i) {
+		g_snprintf(hashp, 3, "%02x", result[i]);
+		hashp += 2;
+	}
+	*/
+	g_free(chal);
+	
+	url = g_string_append(url, md5);
+	g_hash_table_foreach(hash, yahoo_login_page_hash_iter, url);
+	
+	url = g_string_append(url, "&.hash=1&.md5=1 HTTP/1.1\r\n"
+			      "Host: login.yahoo.com\r\n\r\n");
+	g_hash_table_destroy(hash);
+	
+	yd->auth = g_string_free(url, FALSE);
+	if (gaim_proxy_connect(account, "login.yahoo.com", 80, yahoo_got_cookies, gc) != 0) {
+		gaim_connection_error(gc, _("Connection problem"));
+		return;
+	}
+}
+
+#endif /* YAHOO_WEBMESSENGER */
+
 static void yahoo_login(GaimAccount *account) {
 	GaimConnection *gc = gaim_account_get_connection(account);
 	struct yahoo_data *yd = gc->proto_data = g_new0(struct yahoo_data, 1);
@@ -1738,12 +1925,16 @@
 	yd->confs = NULL;
 	yd->conf_id = 2;
 
+#ifndef YAHOO_WEBMESSENGER
 	if (gaim_proxy_connect(account, gaim_account_get_string(account, "server",  YAHOO_PAGER_HOST),
 			  gaim_account_get_int(account, "port", YAHOO_PAGER_PORT),
 			  yahoo_got_connected, gc) != 0) {
 		gaim_connection_error(gc, _("Connection problem"));
 		return;
 	}
+#else
+	gaim_url_fetch(WEBMESSENGER_URL, TRUE, "Gaim/" VERSION, FALSE, yahoo_login_page_cb, gc);
+#endif
 
 }
 
--- a/src/protocols/yahoo/yahoo.h	Fri Oct 03 21:57:44 2003 +0000
+++ b/src/protocols/yahoo/yahoo.h	Fri Oct 03 23:01:13 2003 +0000
@@ -25,6 +25,8 @@
 
 #include "prpl.h"
 
+#define WEBMESSENGER_URL "http://login.yahoo.com/config/login?.src=pg"
+
 enum yahoo_service { /* these are easier to see in hex */
 	YAHOO_SERVICE_LOGON = 1,
 	YAHOO_SERVICE_LOGOFF,
@@ -83,7 +85,8 @@
 	YAHOO_SERVICE_CHATADDINVITE = 0x9d,
 	YAHOO_SERVICE_CHATLOGOUT = 0xa0,
 	YAHOO_SERVICE_CHATPING,
-	YAHOO_SERVICE_COMMENT = 0xa8
+	YAHOO_SERVICE_COMMENT = 0xa8,
+	YAHOO_SERVICE_WEBLOGIN = 0x0226
 };
 
 enum yahoo_status {
@@ -100,6 +103,7 @@
 	YAHOO_STATUS_INVISIBLE = 12,
 	YAHOO_STATUS_CUSTOM = 99,
 	YAHOO_STATUS_IDLE = 999,
+	YAHOO_STATUS_WEBLOGIN = 0x5a55aa55,
 	YAHOO_STATUS_OFFLINE = 0x5a55aa56, /* don't ask */
 	YAHOO_STATUS_TYPING = 0x16
 };
@@ -117,6 +121,7 @@
 	gboolean chat_online;
 	gboolean in_chat;
 	char *chat_name;
+	char *auth;
 };
 
 struct yahoo_pair {
--- a/src/util.c	Fri Oct 03 21:57:44 2003 +0000
+++ b/src/util.c	Fri Oct 03 23:01:13 2003 +0000
@@ -1918,6 +1918,64 @@
 	}
 }
 
+char *
+gaim_url_decode(const char *str)
+{
+	static char buf[BUF_LEN];
+	int i, j = 0;
+	char *bum;
+
+	g_return_val_if_fail(str != NULL, NULL);
+
+	for (i = 0; i < strlen(str); i++) {
+		char hex[3];
+
+		if (str[i] != '%')
+			buf[j++] = str[i];
+		else {
+			strncpy(hex, str + ++i, 2);
+			hex[2] = '\0';
+
+			/* i is pointing to the start of the number */
+			i++;
+
+			/*
+			 * Now it's at the end and at the start of the for loop
+			 * will be at the next character.
+			 */
+			buf[j++] = strtol(hex, NULL, 16);
+		}
+	}
+
+	buf[j] = '\0';
+
+	if (!g_utf8_validate(buf, -1, (const char **)&bum))
+		*bum = '\0';
+
+	return buf;
+}
+
+char *
+gaim_url_encode(const char *str)
+{
+	static char buf[BUF_LEN];
+	int i, j = 0;
+
+	g_return_val_if_fail(str != NULL, NULL);
+
+	for (i = 0; i < strlen(str); i++) {
+		if (isalnum(str[i]))
+			buf[j++] = str[i];
+		else {
+			sprintf(buf + j, "%%%02x", (unsigned char)str[i]);
+			j += 3;
+		}
+	}
+
+	buf[j] = '\0';
+
+	return buf;
+}
 
 /**************************************************************************
  * UTF8 String Functions
--- a/src/util.h	Fri Oct 03 21:57:44 2003 +0000
+++ b/src/util.h	Fri Oct 03 23:01:13 2003 +0000
@@ -454,10 +454,30 @@
 					const char *user_agent, gboolean http11,
 					void (*cb)(void *, const char *, size_t),
 					void *data);
+/**
+ * Decodes a URL into a plain string.
+ *
+ * This will change hex codes and such to their ascii equivalents.
+ *
+ * @param str The string to translate.
+ *
+ * @return The resulting string.
+ */	
+char *gaim_url_decode(const char *str);
+
+/**
+ * Encodes a URL into an escaped string.
+ *
+ * This will change non-alphanumeric characters to hex codes.
+ *
+ * @param str The string to translate.
+ *
+ * @return The resulting string.
+ */
+char *gaim_url_encode(const char *str);
 
 /*@}*/
 
-
 /**************************************************************************
  * UTF8 String Functions
  **************************************************************************/