changeset 8225:9790cda80d52

[gaim-migrate @ 8948] Various bits o' chat code cleanup for oscar. Mostly I just wanted to re-use some code for incoming i18n chat messages. committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Wed, 11 Feb 2004 04:06:16 +0000
parents ad524b8c9c71
children c5e3797e7dbb
files src/protocols/oscar/aim.h src/protocols/oscar/chat.c src/protocols/oscar/im.c src/protocols/oscar/oscar.c src/protocols/oscar/service.c src/protocols/oscar/tlv.c
diffstat 6 files changed, 229 insertions(+), 225 deletions(-) [+]
line wrap: on
line diff
--- a/src/protocols/oscar/aim.h	Wed Feb 11 04:04:48 2004 +0000
+++ b/src/protocols/oscar/aim.h	Wed Feb 11 04:06:16 2004 +0000
@@ -862,6 +862,7 @@
 /* 0x0004 */ faim_export int aim_im_reqparams(aim_session_t *sess);
 /* 0x0006 */ faim_export int aim_im_sendch1_ext(aim_session_t *sess, struct aim_sendimext_args *args);
 /* 0x0006 */ faim_export int aim_im_sendch1(aim_session_t *, const char *destsn, fu16_t flags, const char *msg);
+/* 0x0006 */ faim_export int aim_im_sendch2_chatinvite(aim_session_t *sess, const char *sn, const char *msg, fu16_t exchange, const char *roomname, fu16_t instance);
 /* 0x0006 */ faim_export int aim_im_sendch2_icon(aim_session_t *sess, const char *sn, const fu8_t *icon, int iconlen, time_t stamp, fu16_t iconsum);
 /* 0x0006 */ faim_export int aim_im_sendch2_rtfmsg(aim_session_t *sess, struct aim_sendrtfmsg_args *args);
 /* 0x0006 */ faim_export int aim_im_sendch2_odcrequest(aim_session_t *sess, fu8_t *cookie, const char *sn, const fu8_t *ip, fu16_t port);
@@ -1127,8 +1128,6 @@
 
 faim_export int aim_chatnav_reqrights(aim_session_t *sess, aim_conn_t *conn);
 
-faim_export int aim_chat_invite(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *msg, fu16_t exchange, const char *roomname, fu16_t instance);
-
 faim_export int aim_chatnav_createroom(aim_session_t *sess, aim_conn_t *conn, const char *name, fu16_t exchange);
 faim_export int aim_chat_leaveroom(aim_session_t *sess, const char *name);
 
@@ -1400,6 +1399,7 @@
 faim_internal int aim_tlvlist_add_32(aim_tlvlist_t **list, const fu16_t type, const fu32_t value);
 faim_internal int aim_tlvlist_add_caps(aim_tlvlist_t **list, const fu16_t type, const fu32_t caps);
 faim_internal int aim_tlvlist_add_userinfo(aim_tlvlist_t **list, fu16_t type, aim_userinfo_t *userinfo);
+faim_internal int aim_tlvlist_add_chatroom(aim_tlvlist_t **list, fu16_t type, fu16_t exchange, const char *roomname, fu16_t instance);
 faim_internal int aim_tlvlist_add_frozentlvlist(aim_tlvlist_t **list, fu16_t type, aim_tlvlist_t **tl);
 
 faim_internal int aim_tlvlist_replace_raw(aim_tlvlist_t **list, const fu16_t type, const fu16_t lenth, const fu8_t *value);
--- a/src/protocols/oscar/chat.c	Wed Feb 11 04:04:48 2004 +0000
+++ b/src/protocols/oscar/chat.c	Wed Feb 11 04:06:16 2004 +0000
@@ -85,72 +85,6 @@
 	return 0;
 }
 
-static int aim_addtlvtochain_chatroom(aim_tlvlist_t **list, fu16_t type, fu16_t exchange, const char *roomname, fu16_t instance)
-{
-	fu8_t *buf;
-	int buflen;
-	aim_bstream_t bs;
-
-	buflen = 2 + 1 + strlen(roomname) + 2;
-	
-	if (!(buf = malloc(buflen)))
-		return 0;
-
-	aim_bstream_init(&bs, buf, buflen);
-
-	aimbs_put16(&bs, exchange);
-	aimbs_put8(&bs, strlen(roomname));
-	aimbs_putraw(&bs, roomname, strlen(roomname));
-	aimbs_put16(&bs, instance);
-
-	aim_tlvlist_add_raw(list, type, aim_bstream_curpos(&bs), buf);
-
-	free(buf);
-
-	return 0;
-}
-
-/*
- * Join a room of name roomname.  This is the first step to joining an 
- * already created room.  It's basically a Service Request for 
- * family 0x000e, with a little added on to specify the exchange and room 
- * name.
- */
-faim_export int aim_chat_join(aim_session_t *sess, aim_conn_t *conn, fu16_t exchange, const char *roomname, fu16_t instance)
-{
-	aim_frame_t *fr;
-	aim_snacid_t snacid;
-	aim_tlvlist_t *tl = NULL;
-	struct chatsnacinfo csi;
-	
-	if (!sess || !conn || !roomname || !strlen(roomname))
-		return -EINVAL;
-
-	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512)))
-		return -ENOMEM;
-
-	memset(&csi, 0, sizeof(csi));
-	csi.exchange = exchange;
-	strncpy(csi.name, roomname, sizeof(csi.name));
-	csi.instance = instance;
-
-	snacid = aim_cachesnac(sess, 0x0001, 0x0004, 0x0000, &csi, sizeof(csi));
-	aim_putsnac(&fr->data, 0x0001, 0x0004, 0x0000, snacid);
-
-	/*
-	 * Requesting service chat (0x000e)
-	 */
-	aimbs_put16(&fr->data, 0x000e);
-
-	aim_addtlvtochain_chatroom(&tl, 0x0001, exchange, roomname, instance);
-	aim_tlvlist_write(&fr->data, &tl);
-	aim_tlvlist_free(&tl);
-
-	aim_tx_enqueue(sess, fr);
-
-	return 0; 
-}
-
 faim_internal int aim_chat_readroominfo(aim_bstream_t *bs, struct aim_chat_roominfo *outinfo)
 {
 	int namelen;
@@ -179,96 +113,6 @@
 }
 
 /*
- * conn must be a BOS connection!
- */
-faim_export int aim_chat_invite(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *msg, fu16_t exchange, const char *roomname, fu16_t instance)
-{
-	int i;
-	aim_frame_t *fr;
-	aim_msgcookie_t *cookie;
-	struct aim_invite_priv *priv;
-	fu8_t ckstr[8];
-	aim_snacid_t snacid;
-	aim_tlvlist_t *otl = NULL, *itl = NULL;
-	fu8_t *hdr;
-	int hdrlen;
-	aim_bstream_t hdrbs;
-	
-	if (!sess || !conn || !sn || !msg || !roomname)
-		return -EINVAL;
-
-	if (conn->type != AIM_CONN_TYPE_BOS)
-		return -EINVAL;
-
-	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152+strlen(sn)+strlen(roomname)+strlen(msg))))
-		return -ENOMEM;
-
-	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, sn, strlen(sn)+1);
-	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
-
-	/*
-	 * Cookie
-	 */
-	for (i = 0; i < 8; i++)
-		ckstr[i] = (fu8_t)rand();
-
-	/* XXX should be uncached by an unwritten 'invite accept' handler */
-	if ((priv = malloc(sizeof(struct aim_invite_priv)))) {
-		priv->sn = strdup(sn);
-		priv->roomname = strdup(roomname);
-		priv->exchange = exchange;
-		priv->instance = instance;
-	}
-
-	if ((cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_INVITE, priv)))
-		aim_cachecookie(sess, cookie);
-	else
-		free(priv);
-
-	/* ICBM Header */
-	aimbs_putraw(&fr->data, ckstr, 8); /* Cookie */
-	aimbs_put16(&fr->data, 0x0002); /* Channel */
-	aimbs_put8(&fr->data, strlen(sn)); /* Screename length */
-	aimbs_putraw(&fr->data, sn, strlen(sn)); /* Screenname */
-
-	/*
-	 * TLV t(0005)
-	 *
-	 * Everything else is inside this TLV.
-	 *
-	 * Sigh.  AOL was rather inconsistent right here.  So we have
-	 * to play some minor tricks.  Right inside the type 5 is some
-	 * raw data, followed by a series of TLVs.  
-	 *
-	 */
-	hdrlen = 2+8+16+6+4+4+strlen(msg)+4+2+1+strlen(roomname)+2;
-	hdr = malloc(hdrlen);
-	aim_bstream_init(&hdrbs, hdr, hdrlen);
-	
-	aimbs_put16(&hdrbs, 0x0000); /* Unknown! */
-	aimbs_putraw(&hdrbs, ckstr, sizeof(ckstr)); /* I think... */
-	aim_putcap(&hdrbs, AIM_CAPS_CHAT);
-
-	aim_tlvlist_add_16(&itl, 0x000a, 0x0001);
-	aim_tlvlist_add_noval(&itl, 0x000f);
-	aim_tlvlist_add_raw(&itl, 0x000c, strlen(msg), msg);
-	aim_addtlvtochain_chatroom(&itl, 0x2711, exchange, roomname, instance);
-	aim_tlvlist_write(&hdrbs, &itl);
-	
-	aim_tlvlist_add_raw(&otl, 0x0005, aim_bstream_curpos(&hdrbs), hdr);
-
-	aim_tlvlist_write(&fr->data, &otl);
-
-	free(hdr);
-	aim_tlvlist_free(&itl);
-	aim_tlvlist_free(&otl);
-	
-	aim_tx_enqueue(sess, fr);
-
-	return 0;
-}
-
-/*
  * Subtype 0x0002 - General room information.  Lots of stuff.
  *
  * Values I know are in here but I havent attached
@@ -583,12 +427,12 @@
  *       possibly others
  *  
  */
-static int incomingmsg(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
+static int incomingim_ch3(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
 {
+	int ret = 0, i;
+	aim_rxcallback_t userfunc;	
 	aim_userinfo_t userinfo;
-	aim_rxcallback_t userfunc;	
-	int ret = 0;
-	fu8_t *cookie;
+	fu8_t cookie[8];
 	fu16_t channel;
 	aim_tlvlist_t *otl;
 	char *msg = NULL;
@@ -599,9 +443,10 @@
 	memset(&userinfo, 0, sizeof(aim_userinfo_t));
 
 	/*
-	 * ICBM Cookie.  Uncache it.
+	 * Read ICBM Cookie.
 	 */
-	cookie = aimbs_getraw(bs, 8);
+	for (i = 0; i < 8; i++)
+		cookie[i] = aimbs_get8(bs);
 
 	if ((ck = aim_uncachecookie(sess, cookie, AIM_COOKIETYPE_CHAT))) {
 		free(ck->data);
@@ -611,10 +456,7 @@
 	/*
 	 * Channel ID
 	 *
-	 * Channels 1 and 2 are implemented in the normal ICBM
-	 * parser.
-	 *
-	 * We only do channel 3 here.
+	 * Channel 0x0003 is used for chat messages.
 	 *
 	 */
 	channel = aimbs_get16(bs);
@@ -682,7 +524,6 @@
 		ret = userfunc(sess, rx, &userinfo, len, msg, charset);
 
 	aim_info_free(&userinfo);
-	free(cookie);
 	free(msg);
 	aim_tlvlist_free(&otl);
 
@@ -697,7 +538,7 @@
 	else if ((snac->subtype == 0x0003) || (snac->subtype == 0x0004))
 		return userlistchange(sess, mod, rx, snac, bs);
 	else if (snac->subtype == 0x0006)
-		return incomingmsg(sess, mod, rx, snac, bs);
+		return incomingim_ch3(sess, mod, rx, snac, bs);
 
 	return 0;
 }
--- a/src/protocols/oscar/im.c	Wed Feb 11 04:04:48 2004 +0000
+++ b/src/protocols/oscar/im.c	Wed Feb 11 04:06:16 2004 +0000
@@ -422,6 +422,94 @@
 	return aim_im_sendch1_ext(sess, &args);
 }
 
+/*
+ * Subtype 0x0006 - Send a chat invitation.
+ */
+faim_export int aim_im_sendch2_chatinvite(aim_session_t *sess, const char *sn, const char *msg, fu16_t exchange, const char *roomname, fu16_t instance)
+{
+	aim_conn_t *conn;
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	int i;
+	aim_msgcookie_t *cookie;
+	struct aim_invite_priv *priv;
+	fu8_t ck[8];
+	aim_tlvlist_t *otl = NULL, *itl = NULL;
+	fu8_t *hdr;
+	int hdrlen;
+	aim_bstream_t hdrbs;
+	
+	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)))
+		return -EINVAL;
+
+	if (!sn || !msg || !roomname)
+		return -EINVAL;
+
+	for (i = 0; i < 8; i++)
+		ck[i] = (fu8_t)rand();
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152+strlen(sn)+strlen(roomname)+strlen(msg))))
+		return -ENOMEM;
+
+	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, sn, strlen(sn)+1);
+	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
+
+	/* XXX should be uncached by an unwritten 'invite accept' handler */
+	if ((priv = malloc(sizeof(struct aim_invite_priv)))) {
+		priv->sn = strdup(sn);
+		priv->roomname = strdup(roomname);
+		priv->exchange = exchange;
+		priv->instance = instance;
+	}
+
+	if ((cookie = aim_mkcookie(ck, AIM_COOKIETYPE_INVITE, priv)))
+		aim_cachecookie(sess, cookie);
+	else
+		free(priv);
+
+	/* ICBM Header */
+	aimbs_putraw(&fr->data, ck, 8); /* Cookie */
+	aimbs_put16(&fr->data, 0x0002); /* Channel */
+	aimbs_put8(&fr->data, strlen(sn)); /* Screename length */
+	aimbs_putraw(&fr->data, sn, strlen(sn)); /* Screenname */
+
+	/*
+	 * TLV t(0005)
+	 *
+	 * Everything else is inside this TLV.
+	 *
+	 * Sigh.  AOL was rather inconsistent right here.  So we have
+	 * to play some minor tricks.  Right inside the type 5 is some
+	 * raw data, followed by a series of TLVs.  
+	 *
+	 */
+	hdrlen = 2+8+16+6+4+4+strlen(msg)+4+2+1+strlen(roomname)+2;
+	hdr = malloc(hdrlen);
+	aim_bstream_init(&hdrbs, hdr, hdrlen);
+	
+	aimbs_put16(&hdrbs, 0x0000); /* Unknown! */
+	aimbs_putraw(&hdrbs, ck, sizeof(ck)); /* I think... */
+	aim_putcap(&hdrbs, AIM_CAPS_CHAT);
+
+	aim_tlvlist_add_16(&itl, 0x000a, 0x0001);
+	aim_tlvlist_add_noval(&itl, 0x000f);
+	aim_tlvlist_add_raw(&itl, 0x000c, strlen(msg), msg);
+	aim_tlvlist_add_chatroom(&itl, 0x2711, exchange, roomname, instance);
+	aim_tlvlist_write(&hdrbs, &itl);
+	
+	aim_tlvlist_add_raw(&otl, 0x0005, aim_bstream_curpos(&hdrbs), hdr);
+
+	aim_tlvlist_write(&fr->data, &otl);
+
+	free(hdr);
+	aim_tlvlist_free(&itl);
+	aim_tlvlist_free(&otl);
+	
+	aim_tx_enqueue(sess, fr);
+
+	return 0;
+}
+
 /**
  * Subtype 0x0006 - Send your icon to a given user.
  *
@@ -1887,7 +1975,7 @@
 	memset(&userinfo, 0x00, sizeof(aim_userinfo_t));
 
 	/*
-	 * Read ICBM Cookie.  And throw away.
+	 * Read ICBM Cookie.
 	 */
 	for (i = 0; i < 8; i++)
 		cookie[i] = aimbs_get8(bs);
--- a/src/protocols/oscar/oscar.c	Wed Feb 11 04:04:48 2004 +0000
+++ b/src/protocols/oscar/oscar.c	Wed Feb 11 04:06:16 2004 +0000
@@ -329,33 +329,51 @@
 	return encodingflag;
 }
 
-static fu32_t oscar_encoding_parse(const char *enc)
+/*
+ * Take a string of the form charset="bleh" where bleh is
+ * one of us-ascii, utf-8, iso-8859-1, or unicode-2-0, and 
+ * return a newly allocated string containing bleh.
+ */
+static gchar *oscar_encoding_extract(const char *encoding)
 {
-	char *charset;
-
-	/* If anything goes wrong, fall back on ASCII and print a message */
-	if (enc == NULL) {
-		gaim_debug(GAIM_DEBUG_WARNING, "oscar",
-				   "Encoding was null, that's odd\n");
+	gchar *ret = NULL;
+	char *begin, *end;
+
+	/* Make sure encoding begings with charset= */
+	if (strncmp(encoding, "text/aolrtf; charset=", 21))
+		return NULL;
+
+	begin = strchr(encoding, '"');
+	end = strrchr(encoding, '"');
+
+	if ((begin == NULL) || (end == NULL) || (begin >= end))
+		return NULL;
+
+	ret = g_strndup(begin+1, (end-1) - begin);
+
+	return ret;
+}
+
+/*
+ * Return the flag specifying the given encoding.
+ */
+static fu32_t oscar_encoding_parse(const char *encoding)
+{
+	if ((encoding == NULL) || encoding[0] == '\0') {
+		gaim_debug(GAIM_DEBUG_WARNING, "oscar", "Empty encoding, assuming ASCII\n");
 		return 0;
 	}
-	charset = strstr(enc, "charset=");
-	if (charset == NULL) {
-		gaim_debug(GAIM_DEBUG_WARNING, "oscar",
-				   "No charset specified for info, assuming ASCII\n");
+
+	if (!strcmp(encoding, "us-ascii") || !strcmp(encoding, "utf-8")) {
+		/* UTF-8 is our native encoding, ASCII is a proper subset */
 		return 0;
-	}
-	charset += 8;
-	if (!strcmp(charset, "\"us-ascii\"") || !strcmp(charset, "\"utf-8\"")) {
-		/* UTF-8 is our native charset, ASCII is a proper subset */
-		return 0;
-	} else if (!strcmp(charset, "\"iso-8859-1\"")) {
+	} else if (!strcmp(encoding, "iso-8859-1")) {
 		return AIM_IMFLAGS_ISO_8859_1;
-	} else if (!strcmp(charset, "\"unicode-2-0\"")) {
+	} else if (!strcmp(encoding, "unicode-2-0")) {
 		return AIM_IMFLAGS_UNICODE;
 	} else {
 		gaim_debug(GAIM_DEBUG_WARNING, "oscar",
-				   "Unrecognized character set '%s', using ASCII\n", charset);
+				   "Unrecognized character encoding '%s', falling back to ASCII\n", encoding);
 		return 0;
 	}
 }
@@ -1825,14 +1843,7 @@
 	/* Available message stuff */
 	free(bi->availmsg);
 	if (info->avail != NULL)
-		if (info->avail_encoding) {
-			gchar *enc = g_strdup_printf("charset=\"%s\"", info->avail_encoding);
-			bi->availmsg = oscar_encoding_to_utf8(enc, info->avail, info->avail_len);
-			g_free(enc);
-		} else {
-			/* No explicit encoding means utf8.  Yay. */
-			bi->availmsg = g_strdup(info->avail);
-		}
+		bi->availmsg = oscar_encoding_to_utf8(info->avail_encoding, info->avail, info->avail_len);
 	else
 		bi->availmsg = NULL;
 
@@ -3208,7 +3219,9 @@
 		g_string_append_printf(text, _("Idle: <b>Active</b>"));
 
 	if ((userinfo->flags & AIM_FLAG_AWAY) && (userinfo->away_len > 0) && (userinfo->away != NULL) && (userinfo->away_encoding != NULL)) {
-		away_utf8 = oscar_encoding_to_utf8(userinfo->away_encoding, userinfo->away, userinfo->away_len);
+		gchar *charset = oscar_encoding_extract(userinfo->away_encoding);
+		away_utf8 = oscar_encoding_to_utf8(charset, userinfo->away, userinfo->away_len);
+		g_free(charset);
 		if (away_utf8 != NULL) {
 			g_string_append_printf(text, "<hr>%s", away_utf8);
 			g_free(away_utf8);
@@ -3216,7 +3229,9 @@
 	}
 
 	if ((userinfo->info_len > 0) && (userinfo->info != NULL) && (userinfo->info_encoding != NULL)) {
-		info_utf8 = oscar_encoding_to_utf8(userinfo->info_encoding, userinfo->info, userinfo->info_len);
+		gchar *charset = oscar_encoding_extract(userinfo->info_encoding);
+		info_utf8 = oscar_encoding_to_utf8(charset, userinfo->info, userinfo->info_len);
+		g_free(charset);
 		if (info_utf8 != NULL) {
 			g_string_append_printf(text, "<hr>%s", info_utf8);
 			g_free(info_utf8);
@@ -3412,15 +3427,13 @@
 
 static int gaim_conv_chat_incoming_msg(aim_session_t *sess, aim_frame_t *fr, ...) {
 	GaimConnection *gc = sess->aux_data;
+	struct chat_connection *ccon = find_oscar_chat_by_conn(gc, fr->conn);
+	gchar *utf8;
 	va_list ap;
 	aim_userinfo_t *info;
-	GError *err = NULL;
+	int len;
 	char *msg;
-	char *tmp;
-	int len;
 	char *charset;
-	int convlen;
-	struct chat_connection *ccon = find_oscar_chat_by_conn(gc, fr->conn);
 
 	va_start(ap, fr);
 	info = va_arg(ap, aim_userinfo_t *);
@@ -3429,24 +3442,9 @@
 	charset = va_arg(ap, char *);
 	va_end(ap);
 
-	if (charset) {
-	  if (strcmp(charset, "unicode-2-0") == 0)
-	    charset = "UCS-2BE";
-	} else {
-	  charset = "iso-8859-1";
-	}
-
-	tmp = g_convert(msg, len, "UTF-8", charset, NULL, &convlen, &err);
-	
-	if (err) {
-	  gaim_debug(GAIM_DEBUG_INFO, "oscar",
-		     "Unicode Chat conversion: %s\n", err->message);
-	  tmp = g_strdup(_("(There was an error receiving this message)"));
-	  g_error_free(err);
-	}
-
-	serv_got_chat_in(gc, ccon->id, info->sn, 0, tmp, time((time_t)NULL));
-	g_free(tmp);
+	utf8 = oscar_encoding_to_utf8(charset, msg, len);
+	serv_got_chat_in(gc, ccon->id, info->sn, 0, utf8, time((time_t)NULL));
+	g_free(utf8);
 
 	return 1;
 }
@@ -5334,7 +5332,7 @@
 	if (!ccon)
 		return;
 	
-	aim_chat_invite(od->sess, od->conn, name, message ? message : "",
+	aim_im_sendch2_chatinvite(od->sess, name, message ? message : "",
 			ccon->exchange, ccon->name, 0x0);
 }
 
@@ -5590,7 +5588,9 @@
 		}
 
 		if ((userinfo != NULL) && (userinfo->flags & AIM_FLAG_AWAY) && (userinfo->away_len > 0) && (userinfo->away != NULL) && (userinfo->away_encoding != NULL)) {
-			gchar *away_utf8 = oscar_encoding_to_utf8(userinfo->away_encoding, userinfo->away, userinfo->away_len);
+			gchar *charset = oscar_encoding_extract(userinfo->away_encoding);
+			gchar *away_utf8 = away_utf8 = oscar_encoding_to_utf8(charset, userinfo->away, userinfo->away_len);
+			g_free(charset);
 			if (away_utf8 != NULL) {
 				gchar *tmp1, *tmp2;
 				const char *tmp3;
--- a/src/protocols/oscar/service.c	Wed Feb 11 04:04:48 2004 +0000
+++ b/src/protocols/oscar/service.c	Wed Feb 11 04:06:16 2004 +0000
@@ -97,6 +97,47 @@
 	return aim_genericreq_s(sess, conn, 0x0001, 0x0004, &serviceid);
 }
 
+/*
+ * Join a room of name roomname.  This is the first step to joining an 
+ * already created room.  It's basically a Service Request for 
+ * family 0x000e, with a little added on to specify the exchange and room 
+ * name.
+ */
+faim_export int aim_chat_join(aim_session_t *sess, aim_conn_t *conn, fu16_t exchange, const char *roomname, fu16_t instance)
+{
+	aim_frame_t *fr;
+	aim_snacid_t snacid;
+	aim_tlvlist_t *tl = NULL;
+	struct chatsnacinfo csi;
+	
+	if (!sess || !conn || !roomname || !strlen(roomname))
+		return -EINVAL;
+
+	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512)))
+		return -ENOMEM;
+
+	memset(&csi, 0, sizeof(csi));
+	csi.exchange = exchange;
+	strncpy(csi.name, roomname, sizeof(csi.name));
+	csi.instance = instance;
+
+	snacid = aim_cachesnac(sess, 0x0001, 0x0004, 0x0000, &csi, sizeof(csi));
+	aim_putsnac(&fr->data, 0x0001, 0x0004, 0x0000, snacid);
+
+	/*
+	 * Requesting service chat (0x000e)
+	 */
+	aimbs_put16(&fr->data, 0x000e);
+
+	aim_tlvlist_add_chatroom(&tl, 0x0001, exchange, roomname, instance);
+	aim_tlvlist_write(&fr->data, &tl);
+	aim_tlvlist_free(&tl);
+
+	aim_tx_enqueue(sess, fr);
+
+	return 0; 
+}
+
 /* Subtype 0x0005 - Redirect */
 static int redirect(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
 {
--- a/src/protocols/oscar/tlv.c	Wed Feb 11 04:04:48 2004 +0000
+++ b/src/protocols/oscar/tlv.c	Wed Feb 11 04:06:16 2004 +0000
@@ -510,6 +510,40 @@
 }
 
 /**
+ * Adds the given chatroom info to a TLV chain.
+ *
+ * @param list Destination chain.
+ * @param type TLV type to add.
+ * @param roomname The name of the chat.
+ * @param instance The instance.
+ * @retun The size of the value added.
+ */
+faim_internal int aim_tlvlist_add_chatroom(aim_tlvlist_t **list, fu16_t type, fu16_t exchange, const char *roomname, fu16_t instance)
+{
+	fu8_t *buf;
+	int len;
+	aim_bstream_t bs;
+
+	len = 2 + 1 + strlen(roomname) + 2;
+	
+	if (!(buf = malloc(len)))
+		return 0;
+
+	aim_bstream_init(&bs, buf, len);
+
+	aimbs_put16(&bs, exchange);
+	aimbs_put8(&bs, strlen(roomname));
+	aimbs_putraw(&bs, roomname, strlen(roomname));
+	aimbs_put16(&bs, instance);
+
+	len = aim_tlvlist_add_raw(list, type, aim_bstream_curpos(&bs), buf);
+
+	free(buf);
+
+	return len;
+}
+
+/**
  * Adds a TLV with a zero length to a TLV chain.
  *
  * @param list Destination chain.