view libpurple/protocols/mxit/formcmds.c @ 32389:fac08a235b68

Make the oscar cleanlist function ensure that buddy comments and aliases are valid utf8. If they're not, then we use purple_utf8_salvage() to fix them and save the result in our server list.
author Mark Doliner <mark@kingant.net>
date Mon, 12 Dec 2011 09:01:21 +0000
parents 904686722499
children 3ce30748a583
line wrap: on
line source

/*
 *					MXit Protocol libPurple Plugin
 *
 *					-- MXit Forms & Commands --
 *
 *				Andrew Victor	<libpurple@mxit.com>
 *
 *			(C) Copyright 2009	MXit Lifestyle (Pty) Ltd.
 *				<http://www.mxitlifestyle.com>
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 */


#include "internal.h"
#include <glib/gprintf.h>

#include "purple.h"

#include "protocol.h"
#include "mxit.h"
#include "markup.h"
#include "formcmds.h"

#undef MXIT_DEBUG_COMMANDS

/*
 * the MXit Command identifiers
 */
typedef enum
{
	MXIT_CMD_UNKNOWN = 0,		/* Unknown command */
	MXIT_CMD_CLEAR,				/* Clear (clear) */
	MXIT_CMD_SENDSMS,			/* Send SMS (sendsms) */
	MXIT_CMD_REPLY,				/* Reply (reply) */
	MXIT_CMD_PLATREQ,			/* Platform Request (platreq) */
	MXIT_CMD_SELECTCONTACT,		/* Select Contact (selc) */
	MXIT_CMD_IMAGE,				/* Inline image (img) */
	MXIT_CMD_SCREENCONFIG,		/* Chat-screen config (csc) */
	MXIT_CMD_SCREENINFO,		/* Chat-screen info (csi) */
	MXIT_CMD_IMAGESTRIP,		/* Image Strip (is) */
	MXIT_CMD_TABLE				/* Table (tbl) */
} MXitCommandType;

/* Chat-screen behaviours (bhvr) */
#define SCREEN_NO_HEADINGS		0x01
#define SCREEN_FULLSCREEN		0x02
#define SCREEN_AUTOCLEAR		0x04
#define SCREEN_NO_AUDIO			0x08
#define SCREEN_NO_MSGPREFIX		0x10
#define SCREEN_NOTIFY			0x20
#define SCREEN_PROGRESSBAR		0x40


/*
 * object for an inline image request with an URL
 */
struct ii_url_request
{
	struct RXMsgData*	mx;
	char*				url;
};


/*------------------------------------------------------------------------
 * Callback function invoked when an inline image request to a web site completes.
 *
 *  @param url_data
 *  @param user_data		The Markup message object
 *  @param url_text			The data returned from the WAP site
 *  @param len				The length of the data returned
 *  @param error_message	Descriptive error message
 */
static void mxit_cb_ii_returned(PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message)
{
	struct ii_url_request*	iireq		= (struct ii_url_request*) user_data;
	int*					intptr		= NULL;
	int						id;

#ifdef	MXIT_DEBUG_COMMANDS
	purple_debug_info(MXIT_PLUGIN_ID, "Inline Image returned from %s\n", iireq->url);
#endif

	if (!url_text) {
		/* no reply from the WAP site */
		purple_debug_error(MXIT_PLUGIN_ID, "Error downloading Inline Image from %s.\n", iireq->url);
		goto done;
	}

	/* lets first see if we don't have the inline image already in cache */
	if (g_hash_table_lookup(iireq->mx->session->iimages, iireq->url)) {
		/* inline image found in the cache, so we just ignore this reply */
		goto done;
	}

	/* we now have the inline image, store a copy in the imagestore */
	id = purple_imgstore_add_with_id(g_memdup(url_text, len), len, NULL);

	/* map the inline image id to purple image id */
	intptr = g_malloc(sizeof(int));
	*intptr = id;
	g_hash_table_insert(iireq->mx->session->iimages, iireq->url, intptr);

	iireq->mx->flags |= PURPLE_MESSAGE_IMAGES;

done:
	iireq->mx->img_count--;
	if ((iireq->mx->img_count == 0) && (iireq->mx->converted)) {
		/*
		 * this was the last outstanding emoticon for this message,
		 * so we can now display it to the user.
		 */
		mxit_show_message(iireq->mx);
	}

	g_free(iireq);
}


/*------------------------------------------------------------------------
 * Return the command identifier of this MXit Command.
 *
 *  @param cmd			The MXit command <key,value> map
 *  @return				The MXit command identifier
 */
static MXitCommandType command_type(GHashTable* hash)
{
	char* op;
	char* type;

	op = g_hash_table_lookup(hash, "op");
	if (op) {
		if ( strcmp(op, "cmd") == 0 ) {
			type = g_hash_table_lookup(hash, "type");
			if (type == NULL)								/* no command provided */
				return MXIT_CMD_UNKNOWN;
			else if (strcmp(type, "clear") == 0)			/* clear */
				return MXIT_CMD_CLEAR;
			else if (strcmp(type, "sendsms") == 0)			/* send an SMS */
				return MXIT_CMD_SENDSMS;
			else if (strcmp(type, "reply") == 0)			/* list of options */
				return MXIT_CMD_REPLY;
			else if (strcmp(type, "platreq") == 0)			/* platform request */
				return MXIT_CMD_PLATREQ;
			else if (strcmp(type, "selc") == 0)				/* select contact */
				return MXIT_CMD_SELECTCONTACT;
		}
		else if (strcmp(op, "img") == 0)					/* inline image */
			return MXIT_CMD_IMAGE;
		else if (strcmp(op, "csc") == 0)					/* chat-screen config */
			return MXIT_CMD_SCREENCONFIG;
		else if (strcmp(op, "csi") == 0)					/* chat-screen info */
			return MXIT_CMD_SCREENINFO;
		else if (strcmp(op, "is") == 0)						/* image-strip */
			return MXIT_CMD_IMAGESTRIP;
		else if (strcmp(op, "tbl") == 0)					/* table */
			return MXIT_CMD_TABLE;
	}

	return MXIT_CMD_UNKNOWN;
}


/*------------------------------------------------------------------------
 * Tokenize a MXit Command string into a <key,value> map.
 *
 *  @param cmd			The MXit command string
 *  @return				The <key,value> hash-map, or NULL on error.
 */
static GHashTable* command_tokenize(char* cmd)
{
	GHashTable* hash	= NULL;
	gchar**		parts;
	int			i		= 0;

#ifdef MXIT_DEBUG_COMMANDS
	purple_debug_info(MXIT_PLUGIN_ID, "command: '%s'\n", cmd);
#endif

	/* explode the command into parts */
	parts = g_strsplit(cmd, "|", 0);

	hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);

	/* now break part into a key & value */
	while (parts[i] != NULL) {
		char* value;

		value = strchr(parts[i], '=');		/* find start of value */
		if (value != NULL) {
			*value = '\0';
			value++;
		}

#ifdef MXIT_DEBUG_COMMANDS
		purple_debug_info(MXIT_PLUGIN_ID, "  key='%s' value='%s'\n", parts[i], value);
#endif

		g_hash_table_insert(hash, g_strdup(parts[i]), g_strdup(value));

		i++;
	}

	g_strfreev(parts);

	return hash;
}


/*------------------------------------------------------------------------
 * Process a Clear MXit command.
 *  [::op=cmd|type=clear|clearmsgscreen=true|auto=true|id=12345:]
 *
 *  @param session		The MXit session object
 *  @param from			The sender of the message.
 *  @param hash			The MXit command <key,value> map
 */
static void command_clear(struct MXitSession* session, const char* from, GHashTable* hash)
{
	PurpleConversation *conv;
	char* clearmsgscreen;

	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, from, session->acc);
	if (conv == NULL) {
		purple_debug_error(MXIT_PLUGIN_ID, _( "Conversation with '%s' not found\n" ), from);
		return;
	}

	clearmsgscreen = g_hash_table_lookup(hash, "clearmsgscreen");
	if ( (clearmsgscreen) && (strcmp(clearmsgscreen, "true") == 0) ) {
		/* this is a command to clear the chat screen */
		purple_conversation_clear_message_history(conv);
	}
}


/*------------------------------------------------------------------------
 * Process a Reply MXit command.
 *  [::op=cmd|type=reply|replymsg=back|selmsg=b) Back|displaymsg=Processing|id=12345:]
 *  [::op=cmd|nm=rep|type=reply|replymsg=back|selmsg=b) Back|displaymsg=Processing|id=12345:]
 *
 *  @param mx			The received message data object
 *  @param hash			The MXit command <key,value> map
 */
static void command_reply(struct RXMsgData* mx, GHashTable* hash)
{
	char* replymsg;
	char* selmsg;
	char* nm;

	selmsg = g_hash_table_lookup(hash, "selmsg");			/* selection message */
	replymsg = g_hash_table_lookup(hash, "replymsg");		/* reply message */
	nm = g_hash_table_lookup(hash, "nm");					/* name parameter */

	if ((selmsg == NULL) || (replymsg == NULL))
		return;		/* these parameters are required */

	if (nm) {		/* indicates response must be a structured response */
		gchar*	seltext = g_markup_escape_text(purple_url_decode(selmsg), -1);
		gchar*	replycmd = g_strdup_printf("type=reply|nm=%s|res=%s|err=0", nm, replymsg);

		mxit_add_html_link( mx, replycmd, TRUE, seltext );

		g_free(seltext);
		g_free(replycmd);
	}
	else {
		gchar*	seltext = g_markup_escape_text(purple_url_decode(selmsg), -1);

		mxit_add_html_link( mx, purple_url_decode(replymsg), FALSE, seltext );

		g_free(seltext);
	}
}


/*------------------------------------------------------------------------
 * Process a PlatformRequest MXit command.
 *  [::op=cmd|type=platreq|selmsg=Upgrade MXit|dest=http%3a//m.mxit.com|id=12345:]
 *
 *  @param hash			The MXit command <key,value> map
 *  @param msg			The message to display (as generated so far)
 */
static void command_platformreq(GHashTable* hash, GString* msg)
{
	gchar*	text	= NULL;
	char*	selmsg;
	char*	dest;

	selmsg = g_hash_table_lookup(hash, "selmsg");			/* find the selection message */
	if (selmsg) {
		text = g_markup_escape_text(purple_url_decode(selmsg), -1);
	}

	dest = g_hash_table_lookup(hash, "dest");				/* find the destination */
	if (dest) {
		g_string_append_printf(msg, "<a href=\"%s\">%s</a>", purple_url_decode(dest), (text) ? text : _( "Download" ));		/* add link to display message */
	}

	if (text)
		g_free(text);
}


/*------------------------------------------------------------------------
 * Process an inline image MXit command.
 *  [::op=img|dat=ASDF23408asdflkj2309flkjsadf%3d%3d|algn=1|w=120|h=12|t=100|replymsg=text:]
 *
 *  @param mx			The received message data object
 *  @param hash			The MXit command <key,value> map
 *  @param msg			The message to display (as generated so far)
 */
static void command_image(struct RXMsgData* mx, GHashTable* hash, GString* msg)
{
	const char*	img;
	const char*	reply;
	guchar*		rawimg;
	char		link[256];
	gsize		rawimglen;
	int			imgid;

	img = g_hash_table_lookup(hash, "dat");
	if (img) {
		rawimg = purple_base64_decode(img, &rawimglen);
		//purple_util_write_data_to_file_absolute("/tmp/mxitinline.png", (char*) rawimg, rawimglen);
		imgid = purple_imgstore_add_with_id(rawimg, rawimglen, NULL);
		g_snprintf(link, sizeof(link), "<img id=\"%i\">", imgid);
		g_string_append_printf(msg, "%s", link);
		mx->flags |= PURPLE_MESSAGE_IMAGES;
	}
	else {
		img = g_hash_table_lookup(hash, "src");
		if (img) {
			struct ii_url_request*	iireq;

			iireq = g_new0(struct ii_url_request,1);
			iireq->url = g_strdup(purple_url_decode(img));
			iireq->mx = mx;

			g_string_append_printf(msg, "%s%s>", MXIT_II_TAG, iireq->url);
			mx->got_img = TRUE;

			/* lets first see if we don't have the inline image already in cache */
			if (g_hash_table_lookup(mx->session->iimages, iireq->url)) {
				/* inline image found in the cache, so we do not have to request it from the web */
				g_free(iireq);
			}
			else {
				/* send the request for the inline image */
				purple_debug_info(MXIT_PLUGIN_ID, "sending request for inline image '%s'\n", iireq->url);

				/* request the image (reference: "libpurple/util.h") */
				purple_util_fetch_url(iireq->url, TRUE, NULL, TRUE, -1, mxit_cb_ii_returned, iireq);
				mx->img_count++;
			}
		}
	}

	/* if this is a clickable image, show a click link */
	reply = g_hash_table_lookup(hash, "replymsg");
	if (reply) {
		g_string_append_printf(msg, "\n");
		mxit_add_html_link(mx, reply, FALSE, _( "click here" ));
	}
}


/*------------------------------------------------------------------------
 * Process an Imagestrip MXit command.
 *  [::op=is|nm=status|dat=iVBORw0KGgoAAAA%3d%3d|v=63398792426788|fw=8|fh=8|layer=0:]
 *
 *  @param from			The sender of the message.
 *  @param hash			The MXit command <key,value> map
 */
static void command_imagestrip(struct MXitSession* session, const char* from, GHashTable* hash)
{
	const char* name;
	const char* validator;
	const char* tmp;
	int width, height, layer;

	purple_debug_info(MXIT_PLUGIN_ID, "ImageStrip received from %s\n", from);

	/* image strip name */
	name = g_hash_table_lookup(hash, "nm");

	/* validator */
	validator = g_hash_table_lookup(hash, "v");

	/* image data */
	tmp = g_hash_table_lookup(hash, "dat");
	if (tmp) {
		guchar*		rawimg;
		gsize		rawimglen;
		char*		dir;
		char*		filename;

		/* base64 decode the image data */
		rawimg = purple_base64_decode(tmp, &rawimglen);

		/* save it to a file */
		dir = g_strdup_printf("%s/mxit/imagestrips", purple_user_dir());
		purple_build_dir(dir, S_IRUSR | S_IWUSR | S_IXUSR);		/* ensure directory exists */

		filename = g_strdup_printf("%s/%s-%s-%s.png", dir, from, name, validator);
		purple_util_write_data_to_file_absolute(filename, (char*) rawimg, rawimglen);

		g_free(dir);
		g_free(filename);
	}

	tmp = g_hash_table_lookup(hash, "fw");
	width = atoi(tmp);

	tmp = g_hash_table_lookup(hash, "fh");
	height = atoi(tmp);

	tmp = g_hash_table_lookup(hash, "layer");
	layer = atoi(tmp);

	purple_debug_info(MXIT_PLUGIN_ID, "ImageStrip %s from %s: [w=%i h=%i l=%i validator=%s]\n", name, from, width, height, layer, validator);
}


/*------------------------------------------------------------------------
 * Process a Chat-Screen-Info MXit command.
 *  [::op=csi:]
 *
 *  @param session		The MXit session object
 *  @param from			The sender of the message.
 */
static void command_screeninfo(struct MXitSession* session, const char* from)
{
	char* response;

	purple_debug_info(MXIT_PLUGIN_ID, "Chat Screen Info received from %s\n", from);

	// TODO: Determine width, height, colors of chat-screen.

	response = g_strdup_printf("::type=csi|res=bhvr,0;w,%i;h,%i;col,0.ffffffff,29.ff000000:", 300, 400);

	/* send response back to MXit */
    mxit_send_message( session, from, response, FALSE, TRUE );

	g_free(response);
}


/*------------------------------------------------------------------------
 * Process a Chat-Screen-Configure MXit command.
 *  [::op=csc|bhvr=|menu=<menu>|col=<colors>:]
 *  where:
 *   menu ::= <menuitem> { ";" <menuitem> }
 *     menuitem ::= { type "," <text> "," <name> "," <meta> }
 *   colors ::= <color> { ";" <color> }
 *     color ::= <colorid> "," <ARGB hex color>   
 *
 *  @param session		The MXit session object
 *  @param from			The sender of the message.
 *  @param hash			The MXit command <key,value> map
 */
static void command_screenconfig(struct MXitSession* session, const char* from, GHashTable* hash)
{
	const char* tmp;

	purple_debug_info(MXIT_PLUGIN_ID, "Chat Screen Configure received from %s\n", from);

	/* Behaviour */
	tmp = g_hash_table_lookup(hash, "bhvr");
	if (tmp) {
		purple_debug_info(MXIT_PLUGIN_ID, "  behaviour = %s\n", tmp);
		// TODO: Re-configure conversation screen.
	}

	/* Menu */
	tmp = g_hash_table_lookup(hash, "menu");
	if (tmp) {
		purple_debug_info(MXIT_PLUGIN_ID, "  menu = %s\n", tmp);
		// TODO: Implement conversation-specific sub-menu.
	}

	/* Colours */
	tmp = g_hash_table_lookup(hash, "col");
	if (tmp) {
		purple_debug_info(MXIT_PLUGIN_ID, "  colours = %s\n", tmp);
		// TODO: Re-configuration conversation colors.
	}
}


/*------------------------------------------------------------------------
 * Process a Table Markup MXit command.
 *
 *  @param mx			The received message data object
 *  @param hash			The MXit command <key,value> map
 */
static void command_table(struct RXMsgData* mx, GHashTable* hash)
{
	const char* tmp;
	const char* name;
	int mode;
	int nr_columns = 0, nr_rows = 0;
	gchar** coldata;
	int i, j;

	/* table name */
	name = g_hash_table_lookup(hash, "nm");

	/* number of columns */
	tmp = g_hash_table_lookup(hash, "col");
	nr_columns = atoi(tmp);	

	/* number of rows */
	tmp = g_hash_table_lookup(hash, "row");
	nr_rows = atoi(tmp);

	/* mode */
	tmp = g_hash_table_lookup(hash, "mode");
	mode = atoi(tmp);

	/* table data */
	tmp = g_hash_table_lookup(hash, "d");
	coldata = g_strsplit(tmp, "~", 0);			/* split into entries for each row & column */

	purple_debug_info(MXIT_PLUGIN_ID, "Table %s from %s: [cols=%i rows=%i mode=%i]\n", name, mx->from, nr_columns, nr_rows, mode);

	for (i = 0; i < nr_rows; i++) {
		for (j = 0; j < nr_columns; j++) {
			purple_debug_info(MXIT_PLUGIN_ID, " Row %i Column %i = %s\n", i, j, coldata[i*nr_columns + j]);
		}
	}
}


/*------------------------------------------------------------------------
 * Process a received MXit Command message.
 *
 *  @param mx				The received message data object
 *  @param message			The message text
 *  @return					The length of the command
 */
int mxit_parse_command(struct RXMsgData* mx, char* message)
{
	GHashTable* hash	= NULL;
	char*		start;
	char*		end;

	/* ensure that this is really a command */
	if ( ( message[0] != ':' ) || ( message[1] != ':' ) ) {
		/* this is not a command */
		return 0;
	}

	start = message + 2;
	end = strstr(start, ":");
	if (end) {
		/* end of a command found */
		*end = '\0';		/* terminate command string */

		hash = command_tokenize(start);			/* break into <key,value> pairs */
		if (hash) {
			MXitCommandType type = command_type(hash);

			switch (type) {
				case MXIT_CMD_CLEAR :
					command_clear(mx->session, mx->from, hash);
					break;
				case MXIT_CMD_REPLY :
					command_reply(mx, hash);
					break;
				case MXIT_CMD_PLATREQ :
					command_platformreq(hash, mx->msg);
					break;
				case MXIT_CMD_IMAGE :
					command_image(mx, hash, mx->msg);
					break;
				case MXIT_CMD_SCREENCONFIG :
					command_screenconfig(mx->session, mx->from, hash);
					break;
				case MXIT_CMD_SCREENINFO :
					command_screeninfo(mx->session, mx->from);
					break;
				case MXIT_CMD_IMAGESTRIP :
					command_imagestrip(mx->session, mx->from, hash);
					break;
				case MXIT_CMD_TABLE :
					command_table(mx, hash);
					break;
				default :
					/* command unknown, or not currently supported */
					break;
			}
			g_hash_table_destroy(hash);
		}
		*end = ':';

		return end - message;
	}
	else {
		return 0;
	}
}