view libgaim/protocols/yahoo/ycht.c @ 14837:118fd0dc5b6e

[gaim-migrate @ 17606] Add a "handle" parameter to gaim_proxy_connect(). It seemed like people thought this was a good idea. You can still cancel each gaim_proxy_connect() individually, if needed. I passed in NULL for the handle in most places. It might be better to pass in the gc in more places, but these changes do no harm, and they should help some Yahoo! things, and I wanted to get the API change in. committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Sat, 28 Oct 2006 20:04:03 +0000
parents b6047532826b
children
line wrap: on
line source

/**
 * @file ycht.c The Yahoo! protocol plugin, YCHT protocol stuff.
 *
 * gaim
 *
 * Copyright (C) 2004 Timothy Ringenbach <omarvo@hotmail.com>
 * Liberal amounts of code borrowed from the rest of the Yahoo! prpl.
 *
 * Gaim is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <string.h>

#include "internal.h"
#include "prpl.h"
#include "notify.h"
#include "account.h"
#include "proxy.h"
#include "debug.h"
#include "conversation.h"
#include "util.h"

#include "yahoo.h"
#include "yahoo_packet.h"
#include "ycht.h"
#include "yahoochat.h"

/*
 * dword: YCHT
 * dword: 0x000000AE
 * dword: service
 * word:  status
 * word:  size
 */
#define YAHOO_CHAT_ID (1)
/************************************************************************************
 * Functions to process various kinds of packets.
 ************************************************************************************/
static void ycht_process_login(YchtConn *ycht, YchtPkt *pkt)
{
	GaimConnection *gc = ycht->gc;
	struct yahoo_data *yd = gc->proto_data;

	if (ycht->logged_in)
		return;

	yd->chat_online = TRUE;
	ycht->logged_in = TRUE;

	if (ycht->room)
		ycht_chat_join(ycht, ycht->room);
}

static void ycht_process_logout(YchtConn *ycht, YchtPkt *pkt)
{
	GaimConnection *gc = ycht->gc;
	struct yahoo_data *yd = gc->proto_data;

	yd->chat_online = FALSE;
	ycht->logged_in = FALSE;
}

static void ycht_process_chatjoin(YchtConn *ycht, YchtPkt *pkt)
{
	char *room, *topic;
	GaimConnection *gc = ycht->gc;
	GaimConversation *c = NULL;
	gboolean new_room = FALSE;
	char **members;
	int i;

	room = g_list_nth_data(pkt->data, 0);
	topic = g_list_nth_data(pkt->data, 1);
	if (!g_list_nth_data(pkt->data, 4))
		return;
	if (!room)
		return;

	members = g_strsplit(g_list_nth_data(pkt->data, 4), "\001", 0);
	for (i = 0; members[i]; i++) {
		char *tmp = strchr(members[i], '\002');
		if (tmp)
			*tmp = '\0';
	}

	if (g_list_length(pkt->data) > 5)
		new_room = TRUE;

	if (new_room && ycht->changing_rooms) {
		serv_got_chat_left(gc, YAHOO_CHAT_ID);
		ycht->changing_rooms = FALSE;
		c = serv_got_joined_chat(gc, YAHOO_CHAT_ID, room);
	} else {
		c = gaim_find_chat(gc, YAHOO_CHAT_ID);
	}

	if (topic)
		gaim_conv_chat_set_topic(GAIM_CONV_CHAT(c), NULL, topic);

	for (i = 0; members[i]; i++) {
		if (new_room) {
			/*if (!strcmp(members[i], gaim_connection_get_display_name(ycht->gc)))
				continue;*/
			gaim_conv_chat_add_user(GAIM_CONV_CHAT(c), members[i], NULL, GAIM_CBFLAGS_NONE, TRUE);
		} else {
			yahoo_chat_add_user(GAIM_CONV_CHAT(c), members[i], NULL);
		}
	}

	g_strfreev(members);
}

static void ycht_process_chatpart(YchtConn *ycht, YchtPkt *pkt)
{
	char *room, *who;

	room = g_list_nth_data(pkt->data, 0);
	who = g_list_nth_data(pkt->data, 1);

	if (who && room) {
		GaimConversation *c = gaim_find_chat(ycht->gc, YAHOO_CHAT_ID);
		if (c && !gaim_utf8_strcasecmp(gaim_conversation_get_name(c), room))
			gaim_conv_chat_remove_user(GAIM_CONV_CHAT(c), who, NULL);

	}
}

static void ycht_progress_chatmsg(YchtConn *ycht, YchtPkt *pkt)
{
	char *who, *what, *msg;
	GaimConversation *c;
	GaimConnection *gc = ycht->gc;

	who = g_list_nth_data(pkt->data, 1);
	what = g_list_nth_data(pkt->data, 2);

	if (!who || !what)
		return;

	c = gaim_find_chat(gc, YAHOO_CHAT_ID);
	if (!c)
		return;

	msg = yahoo_string_decode(gc, what, 1);
	what = yahoo_codes_to_html(msg);
	g_free(msg);

	if (pkt->service == YCHT_SERVICE_CHATMSG_EMOTE) {
		char *tmp = g_strdup_printf("/me %s", what);
		g_free(what);
		what = tmp;
	}

	serv_got_chat_in(gc, YAHOO_CHAT_ID, who, 0, what, time(NULL));
	g_free(what);
}

static void ycht_progress_online_friends(YchtConn *ycht, YchtPkt *pkt)
{
#if 0
	GaimConnection *gc = ycht->gc;
	struct yahoo_data *yd = gc->proto_data;

	if (ycht->logged_in)
		return;

	yd->chat_online = TRUE;
	ycht->logged_in = TRUE;

	if (ycht->room)
		ycht_chat_join(ycht, ycht->room);
#endif
}

/*****************************************************************************
 * Functions dealing with YCHT packets and their contents directly.
 *****************************************************************************/
static void ycht_packet_dump(const guchar *data, int len)
{
#ifdef YAHOO_YCHT_DEBUG
	int i;

	gaim_debug(GAIM_DEBUG_MISC, "yahoo", "");

	for (i = 0; i + 1 < len; i += 2) {
		if ((i % 16 == 0) && i) {
			gaim_debug(GAIM_DEBUG_MISC, NULL, "\n");
			gaim_debug(GAIM_DEBUG_MISC, "yahoo", "");
		}

		gaim_debug(GAIM_DEBUG_MISC, NULL, "%02hhx%02hhx ", data[i], data[i + 1]);
	}
	if (i < len)
		gaim_debug(GAIM_DEBUG_MISC, NULL, "%02hhx", data[i]);

	gaim_debug(GAIM_DEBUG_MISC, NULL, "\n");
	gaim_debug(GAIM_DEBUG_MISC, "yahoo", "");

	for (i = 0; i < len; i++) {
		if ((i % 16 == 0) && i) {
			gaim_debug(GAIM_DEBUG_MISC, NULL, "\n");
			gaim_debug(GAIM_DEBUG_MISC, "yahoo", "");
		}

		if (g_ascii_isprint(data[i]))
			gaim_debug(GAIM_DEBUG_MISC, NULL, "%c ", data[i]);
		else
			gaim_debug(GAIM_DEBUG_MISC, NULL, ". ");
	}

	gaim_debug(GAIM_DEBUG_MISC, NULL, "\n");
#endif
}

static YchtPkt *ycht_packet_new(guint version, guint service, int status)
{
	YchtPkt *ret;

	ret = g_new0(YchtPkt, 1);

	ret->version = version;
	ret->service = service;
	ret->status = status;

	return ret;
}

static void ycht_packet_append(YchtPkt *pkt, const char *str)
{
	g_return_if_fail(pkt != NULL);
	g_return_if_fail(str != NULL);

	pkt->data = g_list_append(pkt->data, g_strdup(str));
}

static int ycht_packet_length(YchtPkt *pkt)
{
	int ret;
	GList *l;

	ret = YCHT_HEADER_LEN;

	for (l = pkt->data; l; l = l->next) {
		ret += strlen(l->data);
		if (l->next)
			ret += strlen(YCHT_SEP);
	}

	return ret;
}

static void ycht_packet_send_write_cb(gpointer data, gint source, GaimInputCondition cond)
{
	YchtConn *ycht = data;
	int ret, writelen;

	writelen = gaim_circ_buffer_get_max_read(ycht->txbuf);

	if (writelen == 0) {
		gaim_input_remove(ycht->tx_handler);
		ycht->tx_handler = 0;
		return;
	}

	ret = write(ycht->fd, ycht->txbuf->outptr, writelen);

	if (ret < 0 && errno == EAGAIN)
		return;
	else if (ret <= 0) {
		/* TODO: error handling */
/*
		gaim_connection_error(gaim_account_get_connection(irc->account),
			      _("Server has disconnected"));
*/
		return;
	}

	gaim_circ_buffer_mark_read(ycht->txbuf, ret);

}

static void ycht_packet_send(YchtConn *ycht, YchtPkt *pkt)
{
	int len, pos, written;
	char *buf;
	GList *l;

	g_return_if_fail(ycht != NULL);
	g_return_if_fail(pkt != NULL);
	g_return_if_fail(ycht->fd != -1);

	pos = 0;
	len = ycht_packet_length(pkt);
	buf = g_malloc(len);

	memcpy(buf + pos, "YCHT", 4); pos += 4;
	pos += yahoo_put32(buf + pos, pkt->version);
	pos += yahoo_put32(buf + pos, pkt->service);
	pos += yahoo_put16(buf + pos, pkt->status);
	pos += yahoo_put16(buf + pos, len - YCHT_HEADER_LEN);

	for (l = pkt->data; l; l = l->next) {
		int slen = strlen(l->data);
		memcpy(buf + pos, l->data, slen); pos += slen;

		if (l->next) {
			memcpy(buf + pos, YCHT_SEP, strlen(YCHT_SEP));
			pos += strlen(YCHT_SEP);
		}
	}

	if (!ycht->tx_handler)
		written = write(ycht->fd, buf, len);
	else {
		written = -1;
		errno = EAGAIN;
	}

	if (written < 0 && errno == EAGAIN)
		written = 0;
	else if (written <= 0) {
		/* TODO: Error handling (was none before NBIO changes) */
		written = 0;
	}

	if (written < len) {
		if (!ycht->tx_handler)
			ycht->tx_handler = gaim_input_add(ycht->fd,
				GAIM_INPUT_WRITE, ycht_packet_send_write_cb,
				ycht);
		gaim_circ_buffer_append(ycht->txbuf, buf + written,
			len - written);
	}

	g_free(buf);
}

static void ycht_packet_read(YchtPkt *pkt, const char *buf, int len)
{
	const char *pos = buf;
	const char *needle;
	char *tmp, *tmp2;
	int i = 0;

	while (len > 0 && (needle = g_strstr_len(pos, len, YCHT_SEP))) {
		tmp = g_strndup(pos, needle - pos);
		pkt->data = g_list_append(pkt->data, tmp);
		len -= needle - pos + strlen(YCHT_SEP);
		pos = needle + strlen(YCHT_SEP);
		tmp2 = g_strescape(tmp, NULL);
		gaim_debug_misc("yahoo", "Data[%d]:\t%s\n", i++, tmp2);
		g_free(tmp2);
	}

	if (len) {
		tmp = g_strndup(pos, len);
		pkt->data = g_list_append(pkt->data, tmp);
		tmp2 = g_strescape(tmp, NULL);
		gaim_debug_misc("yahoo", "Data[%d]:\t%s\n", i, tmp2);
		g_free(tmp2);
	};

	gaim_debug_misc("yahoo", "--==End of incoming YCHT packet==--\n");
}

static void ycht_packet_process(YchtConn *ycht, YchtPkt *pkt)
{
	if (pkt->data && !strncmp(pkt->data->data, "*** Danger Will Robinson!!!", strlen("*** Danger Will Robinson!!!")))
		return;

	switch (pkt->service) {
	case YCHT_SERVICE_LOGIN:
		ycht_process_login(ycht, pkt);
		break;
	case YCHT_SERVICE_LOGOUT:
		ycht_process_logout(ycht, pkt);
		break;
	case YCHT_SERVICE_CHATJOIN:
		ycht_process_chatjoin(ycht, pkt);
		break;
	case YCHT_SERVICE_CHATPART:
		ycht_process_chatpart(ycht, pkt);
		break;
	case YCHT_SERVICE_CHATMSG:
	case YCHT_SERVICE_CHATMSG_EMOTE:
		ycht_progress_chatmsg(ycht, pkt);
		break;
	case YCHT_SERVICE_ONLINE_FRIENDS:
		ycht_progress_online_friends(ycht, pkt);
		break;
	default:
		gaim_debug_warning("yahoo", "YCHT: warning, unhandled service 0x%02x\n", pkt->service);
	}
}

static void ycht_packet_free(YchtPkt *pkt)
{
	GList *l;

	g_return_if_fail(pkt != NULL);

	for (l = pkt->data; l; l = l->next)
		g_free(l->data);
	g_list_free(pkt->data);
	g_free(pkt);
}

/************************************************************************************
 * Functions dealing with connecting and disconnecting and reading data into YchtPkt
 * structs, and all that stuff.
 ************************************************************************************/

void ycht_connection_close(YchtConn *ycht)
{
	struct yahoo_data *yd = ycht->gc->proto_data;

	if (yd) {
		yd->ycht = NULL;
		yd->chat_online = FALSE;
	}

	if (ycht->fd > 0)
		close(ycht->fd);
	if (ycht->inpa)
		gaim_input_remove(ycht->inpa);

	if (ycht->tx_handler)
		gaim_input_remove(ycht->tx_handler);

	gaim_circ_buffer_destroy(ycht->txbuf);

	g_free(ycht->rxqueue);

	g_free(ycht);
}

static void ycht_connection_error(YchtConn *ycht, const gchar *error)
{

	gaim_notify_info(ycht->gc, NULL, _("Connection problem with the YCHT server."), error);
	ycht_connection_close(ycht);
}

static void ycht_pending(gpointer data, gint source, GaimInputCondition cond)
{
	YchtConn *ycht = data;
	char buf[1024];
	int len;

	len = read(ycht->fd, buf, sizeof(buf));

	if (len < 0) {
		gchar *tmp;

		if (errno == EAGAIN)
			/* No worries */
			return;

		tmp = g_strdup_printf(_("Lost connection with server\n%s"),
				strerror(errno));
		ycht_connection_error(ycht, tmp);
		g_free(tmp);
		return;
	} else if (len == 0) {
		ycht_connection_error(ycht, _("Server closed the connection."));
		return;
	}

	ycht->rxqueue = g_realloc(ycht->rxqueue, len + ycht->rxlen);
	memcpy(ycht->rxqueue + ycht->rxlen, buf, len);
	ycht->rxlen += len;

	while (1) {
		YchtPkt *pkt;
		int pos = 0;
		int pktlen;
		guint service;
		guint version;
		gint status;

		if (ycht->rxlen < YCHT_HEADER_LEN)
			return;

		if (strncmp("YCHT", (char *)ycht->rxqueue, 4) != 0)
			gaim_debug_error("yahoo", "YCHT: protocol error.\n");

		pos += 4; /* YCHT */

		version = yahoo_get32(ycht->rxqueue + pos); pos += 4;
		service = yahoo_get32(ycht->rxqueue + pos); pos += 4;
		status = yahoo_get16(ycht->rxqueue + pos); pos += 2;
		pktlen  = yahoo_get16(ycht->rxqueue + pos); pos += 2;
		gaim_debug(GAIM_DEBUG_MISC, "yahoo",
				   "ycht: %d bytes to read, rxlen is %d\n", pktlen, ycht->rxlen);

		if (ycht->rxlen < (YCHT_HEADER_LEN + pktlen))
			return;

		gaim_debug_misc("yahoo", "--==Incoming YCHT packet==--\n");
		gaim_debug(GAIM_DEBUG_MISC, "yahoo",
			   "YCHT Service: 0x%02x Version: 0x%02x Status: 0x%02x\n",
			   service, version, status);
		ycht_packet_dump(ycht->rxqueue, YCHT_HEADER_LEN + pktlen);

		pkt = ycht_packet_new(version, service, status);
		ycht_packet_read(pkt, (char *)ycht->rxqueue + pos, pktlen);

		ycht->rxlen -= YCHT_HEADER_LEN + pktlen;
		if (ycht->rxlen) {
			guchar *tmp = g_memdup(ycht->rxqueue + YCHT_HEADER_LEN + pktlen, ycht->rxlen);
			g_free(ycht->rxqueue);
			ycht->rxqueue = tmp;
		} else {
			g_free(ycht->rxqueue);
			ycht->rxqueue = NULL;
		}

		ycht_packet_process(ycht, pkt);

		ycht_packet_free(pkt);
	}
}

static void ycht_got_connected(gpointer data, gint source, const gchar *error_message)
{
	YchtConn *ycht = data;
	GaimConnection *gc = ycht->gc;
	struct yahoo_data *yd = gc->proto_data;
	YchtPkt *pkt;
	char *buf;

	if (source < 0) {
		ycht_connection_error(ycht, _("Unable to connect."));
		return;
	}

	ycht->fd = source;

	pkt = ycht_packet_new(YCHT_VERSION, YCHT_SERVICE_LOGIN, 0);

	buf = g_strdup_printf("%s\001Y=%s; T=%s", gaim_connection_get_display_name(gc), yd->cookie_y, yd->cookie_t);
	ycht_packet_append(pkt, buf);
	g_free(buf);

	ycht_packet_send(ycht, pkt);

	ycht_packet_free(pkt);

	ycht->inpa = gaim_input_add(ycht->fd, GAIM_INPUT_READ, ycht_pending, ycht);
}

void ycht_connection_open(GaimConnection *gc)
{
	YchtConn *ycht;
	struct yahoo_data *yd = gc->proto_data;
	GaimAccount *account = gaim_connection_get_account(gc);

	ycht = g_new0(YchtConn, 1);
	ycht->gc = gc;
	ycht->fd = -1;

	yd->ycht = ycht;

	if (gaim_proxy_connect(NULL, account,
	                       gaim_account_get_string(account, "ycht-server",  YAHOO_YCHT_HOST),
	                       gaim_account_get_int(account, "ycht-port", YAHOO_YCHT_PORT),
	                       ycht_got_connected, ycht) == NULL)
	{
		ycht_connection_error(ycht, _("Connection problem"));
		return;
	}
}

/*******************************************************************************************
 * These are functions called because the user did something.
 *******************************************************************************************/

void ycht_chat_join(YchtConn *ycht, const char *room)
{
	YchtPkt *pkt;
	char *tmp;

	tmp = g_strdup(room);
	g_free(ycht->room);
	ycht->room = tmp;

	if (!ycht->logged_in)
		return;

	ycht->changing_rooms = TRUE;
	pkt = ycht_packet_new(YCHT_VERSION, YCHT_SERVICE_CHATJOIN, 0);
	ycht_packet_append(pkt, ycht->room);
	ycht_packet_send(ycht, pkt);
	ycht_packet_free(pkt);
}

int ycht_chat_send(YchtConn *ycht, const char *room, const char *what)
{
	YchtPkt *pkt;
	char *msg1, *msg2, *buf;

	if (strcmp(room, ycht->room))
		gaim_debug_warning("yahoo", "uhoh, sending to the wrong room!\n");

	pkt = ycht_packet_new(YCHT_VERSION, YCHT_SERVICE_CHATMSG, 0);

	msg1 = yahoo_html_to_codes(what);
	msg2 = yahoo_string_encode(ycht->gc, msg1, NULL);
	g_free(msg1);

	buf = g_strdup_printf("%s\001%s", ycht->room, msg2);
	ycht_packet_append(pkt, buf);
	g_free(msg2);
	g_free(buf);

	ycht_packet_send(ycht, pkt);
	ycht_packet_free(pkt);
	return 1;
}

void ycht_chat_leave(YchtConn *ycht, const char *room, gboolean logout)
{
	if (logout)
		ycht_connection_close(ycht);
}

void ycht_chat_send_invite(YchtConn *ycht, const char *room, const char *buddy, const char *msg)
{
}

void ycht_chat_goto_user(YchtConn *ycht, const char *name)
{
}

void ycht_chat_send_keepalive(YchtConn *ycht)
{
	YchtPkt *pkt;

	pkt = ycht_packet_new(YCHT_VERSION, YCHT_SERVICE_PING, 0);
	ycht_packet_send(ycht, pkt);
	ycht_packet_free(pkt);
}