view libpurple/protocols/myspace/message.c @ 18879:e0cac5db762b

In msim_msg_pack_element_dict(), separate keys and values with '=' for the MSIM_TYPE_DICTIONARY type. This should allow the dictionary type to work with MySpace's servers. Tested in msim_test_msg(). Also removed msim_test_xml(), since it was more of a playground than a formal test of anything. MsimMessage's dictionary type is now thought to be fully-functional, but it is not yet used anywhere.
author Jeffrey Connelly <jaconnel@calpoly.edu>
date Tue, 07 Aug 2007 02:11:15 +0000
parents b2d81d13f015
children f41db253c1af
line wrap: on
line source

/** MySpaceIM protocol messages
 *
 * \author Jeff Connelly
 *
 * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
 *
 * 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 "myspace.h"
#include "message.h"

static gchar *msim_unescape_or_escape(gchar *msg, gboolean escape);
static void msim_msg_free_element(gpointer data, gpointer user_data);
static void msim_msg_debug_string_element(gpointer data, gpointer user_data);
static gchar *msim_msg_pack_using(MsimMessage *msg, GFunc gf, const gchar *sep, const gchar *begin, const gchar *end);
static gchar *msim_msg_pack_element_data(MsimMessageElement *elem);
static GList *msim_msg_get_node(MsimMessage *msg, const gchar *name);
static MsimMessage *msim_msg_new_v(va_list argp);

/* Replacement codes to be replaced with associated replacement text,
 * used for protocol message escaping / unescaping. */
static gchar* msim_replacement_code[] = { "/1", "/2", /* "/3", */ NULL };
static gchar* msim_replacement_text[] = { "/", "\\", /* "|", */ NULL };

/**
 * Unescape or escape a protocol message.
 *
 * @param msg The message to be unescaped or escaped. WILL BE FREED.
 * @param escape TRUE to escape, FALSE to unescape.
 *
 * @return The unescaped or escaped message. Caller must g_free().
 */
static gchar *
msim_unescape_or_escape(gchar *msg, gboolean escape)
{
	gchar *tmp, *code, *text;
	guint i;

	/* Replace each code in msim_replacement_code with
	 * corresponding entry in msim_replacement_text. */
	for (i = 0; (code = msim_replacement_code[i])
		   	&& (text = msim_replacement_text[i]); ++i)
	{
		if (escape)
		{
			tmp = str_replace(msg, text, code);
		}
		else
		{
			tmp = str_replace(msg, code, text);
		}
		g_free(msg);
		msg = tmp;
	}
	
	return msg;
}

/**
 * Escape a protocol message.
 *
 * @return The escaped message. Caller must g_free().
 */
gchar *
msim_escape(const gchar *msg)
{
	return msim_unescape_or_escape(g_strdup(msg), TRUE);
}

gchar *
msim_unescape(const gchar *msg)
{
	return msim_unescape_or_escape(g_strdup(msg), FALSE);
}

/** Create a new MsimMessage. 
 * 
 * @param not_empty FALSE if message is empty, TRUE if variadic arguments follow.
 * @param ... A sequence of gchar* key/type/value triplets, terminated with NULL. 
 *
 * See msim_msg_append() documentation for details on types.
 */
MsimMessage *
msim_msg_new(gboolean not_empty, ...)
{
	va_list argp;

	va_start(argp, not_empty);

	if (not_empty)
		return msim_msg_new_v(argp);
	else
		return NULL;
}

/** Create a new message from va_list and its first argument.
 *
 * @param argp A va_list of variadic arguments, already started with va_start(). Will be va_end()'d.
 * @return New MsimMessage *, must be freed with msim_msg_free().
 *
 * For internal use - users probably want msim_msg_new() or msim_send().
 */
static MsimMessage *
msim_msg_new_v(va_list argp)
{
	gchar *key, *value;
	MsimMessageType type;
	MsimMessage *msg;

	/* Begin with an empty message. */
	msg = NULL;

	/* Read key, type, value triplets until NULL. */
	do
	{
		key = va_arg(argp, gchar *);
		if (!key)
		{
			break;
		}

		type = va_arg(argp, int);

		/* Interpret variadic arguments. */
		switch (type)
		{
			case MSIM_TYPE_INTEGER: 
			case MSIM_TYPE_BOOLEAN: 
				msg = msim_msg_append(msg, key, type, GUINT_TO_POINTER(va_arg(argp, int)));
				break;
				
			case MSIM_TYPE_STRING:
				value = va_arg(argp, char *);

				g_return_val_if_fail(value != NULL, FALSE);

				msg = msim_msg_append(msg, key, type, value);
				break;

			case MSIM_TYPE_BINARY:
                {
                    GString *gs;

                    gs = va_arg(argp, GString *);

                    g_return_val_if_fail(gs != NULL, FALSE);

                    /* msim_msg_free() will free this GString the caller created. */
                    msg = msim_msg_append(msg, key, type, gs);
                    break;
                }

            case MSIM_TYPE_LIST:
                {
                    GList *gl;

                    gl = va_arg(argp, GList *);

                    g_return_val_if_fail(gl != NULL, FALSE);

                    msg = msim_msg_append(msg, key, type, gl);
                    break;
                }

            case MSIM_TYPE_DICTIONARY:
                {
                    MsimMessage *dict;

                    dict = va_arg(argp, MsimMessage *);

                    g_return_val_if_fail(dict != NULL, FALSE);

                    msg = msim_msg_append(msg, key, type, dict);
                    break;
                }

			default:
				purple_debug_info("msim", "msim_send: unknown type %d\n", type);
				break;
		}
	} while(key);
	va_end(argp);	

	return msg;
}

/** Perform a deep copy on a GList * of gchar * strings. Free with msim_msg_list_free(). */
GList *
msim_msg_list_copy(GList *old)
{
    GList *new_list;

    new_list = NULL;

    /* Deep copy (g_list_copy is shallow). Copy each string. */
    for (; old != NULL; old = g_list_next(old))
    {
        new_list = g_list_append(new_list, g_strdup(old->data));
    }

    return new_list;
}

/** Free a GList * of gchar * strings. */
void
msim_msg_list_free(GList *l)
{

    for (; l != NULL; l = g_list_next(l))
    {
        g_free((gchar *)(l->data));
    }
    g_list_free(l);
}

/** Parse a |-separated string into a new GList. Free with msim_msg_list_free(). */
GList *
msim_msg_list_parse(const gchar *raw)
{
    gchar **array;
    GList *list;
    guint i;

    array = g_strsplit(raw, "|", 0);
    list = NULL;
    
    for (i = 0; array[i] != NULL; ++i)
    {
        list = g_list_append(list, g_strdup(array[i]));
    }

    g_strfreev(array);

    return list;
}

/** Clone an individual element.
 *
 * @param data MsimMessageElement * to clone.
 * @param user_data Pointer to MsimMessage * to add cloned element to.
 */
static void 
msim_msg_clone_element(gpointer data, gpointer user_data)
{
	MsimMessageElement *elem;
	MsimMessage **new;
	gpointer new_data;

	elem = (MsimMessageElement *)data;
	new = (MsimMessage **)user_data;

	switch (elem->type)
	{
		case MSIM_TYPE_BOOLEAN:
		case MSIM_TYPE_INTEGER:
			new_data = elem->data;
			break;

		case MSIM_TYPE_RAW:
		case MSIM_TYPE_STRING:
			new_data = g_strdup((gchar *)elem->data);
			break;

        case MSIM_TYPE_LIST:
            new_data = (gpointer)msim_msg_list_copy((GList *)(elem->data));
            break;

		case MSIM_TYPE_BINARY:
			{
				GString *gs;

				gs = (GString *)elem->data;

				new_data = g_string_new_len(gs->str, gs->len);
			}
			break;
        case MSIM_TYPE_DICTIONARY:
            {
                MsimMessage *dict;

                dict = (MsimMessage *)elem->data;

                new_data = msim_msg_clone(dict);
            }
            break;

		default:
			purple_debug_info("msim", "msim_msg_clone_element: unknown type %d\n", elem->type);
			g_return_if_fail(NULL);
	}

	/* Append cloned data. Note that the 'name' field is a static string, so it
	 * never needs to be copied nor freed. */
	*new = msim_msg_append(*new, elem->name, elem->type, new_data);
}

/** Clone an existing MsimMessage. 
 *
 * @return Cloned message; caller should free with msim_msg_free().
 */
MsimMessage *
msim_msg_clone(MsimMessage *old)
{
	MsimMessage *new;

	if (old == NULL)
		return NULL;

	new = msim_msg_new(FALSE);

	g_list_foreach(old, msim_msg_clone_element, &new);

	return new;
}

/** Free an individual message element. 
 *
 * @param data MsimMessageElement * to free.
 * @param user_data Not used; required to match g_list_foreach() callback prototype.
 */
static void 
msim_msg_free_element(gpointer data, gpointer user_data)
{
	MsimMessageElement *elem;

	elem = (MsimMessageElement *)data;

	switch (elem->type)
	{
		case MSIM_TYPE_BOOLEAN:
		case MSIM_TYPE_INTEGER:
			/* Integer value stored in gpointer - no need to free(). */
			break;

		case MSIM_TYPE_RAW:
		case MSIM_TYPE_STRING:
			/* Always free strings - caller should have g_strdup()'d if
			 * string was static or temporary and not to be freed. */
			g_free(elem->data);
			break;

		case MSIM_TYPE_BINARY:
			/* Free the GString itself and the binary data. */
			g_string_free((GString *)elem->data, TRUE);
			break;

		case MSIM_TYPE_DICTIONARY:
            msim_msg_free((MsimMessage *)elem->data);
			break;
			
		case MSIM_TYPE_LIST:
            g_list_free((GList *)elem->data);
			break;

		default:
			purple_debug_info("msim", "msim_msg_free_element: not freeing unknown type %d\n", elem->type);
			break;
	}

	g_free(elem);
}

/** Free a complete message. */
void 
msim_msg_free(MsimMessage *msg)
{
	if (!msg)
	{
		/* already free as can be */
		return;
	}

    msim_msg_dump("msim_msg_free: freeing %s", msg);

	g_list_foreach(msg, msim_msg_free_element, NULL);
	g_list_free(msg);
}

/** Send an existing MsimMessage. */
gboolean 
msim_msg_send(MsimSession *session, MsimMessage *msg)
{
	gchar *raw;
	gboolean success;
	
	raw = msim_msg_pack(msg);
    g_return_val_if_fail(raw != NULL, FALSE);
	success = msim_send_raw(session, raw);
	g_free(raw);

	msim_msg_dump("msim_msg_send()ing %s\n", msg);
	
	return success;
}

/**
 *
 * Send a message to the server, whose contents is specified using 
 * variable arguments.
 *
 * @param session
 * @param ... A sequence of gchar* key/type/value triplets, terminated with NULL. 
 *
 * This function exists for coding convenience: it allows a message to be created
 * and sent in one line of code. Internally it calls msim_msg_send(). 
 *
 * IMPORTANT: See msim_msg_append() documentation for details on element types.
 *
 */
gboolean 
msim_send(MsimSession *session, ...)
{
	gboolean success;
	MsimMessage *msg;
	va_list argp;
    
	va_start(argp, session);
	msg = msim_msg_new_v(argp);

	/* Actually send the message. */
	success = msim_msg_send(session, msg);

	/* Cleanup. */
	msim_msg_free(msg);

	return success;
}

/** Create a new MsimMessageElement * - must be g_free()'d. 
 *
 * For internal use; users probably want msim_msg_append() or msim_msg_insert_before(). 
 */
static MsimMessageElement *
msim_msg_element_new(const gchar *name, MsimMessageType type, gpointer data)
{
	MsimMessageElement *elem;

	elem = g_new0(MsimMessageElement, 1);

	elem->name = name;
	elem->type = type;
	elem->data = data;

	return elem;
}


/** Append a new element to a message. 
 *
 * @param name Textual name of element (static string, neither copied nor freed).
 * @param type An MSIM_TYPE_* code.
 * @param data Pointer to data, see below.
 *
 * @return The new message - must be assigned to as with GList*. For example:
 *
 * 		msg = msim_msg_append(msg, ...)
 *
 * The data parameter depends on the type given:
 *
 * * MSIM_TYPE_INTEGER: Use GUINT_TO_POINTER(x).
 *
 * * MSIM_TYPE_BINARY: Same as integer, non-zero is TRUE and zero is FALSE.
 *
 * * MSIM_TYPE_STRING: gchar *. The data WILL BE FREED - use g_strdup() if needed.
 *
 * * MSIM_TYPE_RAW: gchar *. The data WILL BE FREED - use g_strdup() if needed.
 *
 * * MSIM_TYPE_BINARY: g_string_new_len(data, length). The data AND GString will be freed.
 *
 * * MSIM_TYPE_DICTIONARY: An MsimMessage *. Freed when message is destroyed.
 *
 * * MSIM_TYPE_LIST: GList * of gchar *. Again, everything will be freed.
 *
 * */
MsimMessage *
msim_msg_append(MsimMessage *msg, const gchar *name, 
		MsimMessageType type, gpointer data)
{
	return g_list_append(msg, msim_msg_element_new(name, type, data));
}

/** Insert a new element into a message, before the given element name.
 *
 * @param name_before Name of the element to insert the new element before. If 
 * 					  could not be found or NULL, new element will be inserted at end.
 *
 * See msim_msg_append() for usage of other parameters, and an important note about return value.
 */
MsimMessage *
msim_msg_insert_before(MsimMessage *msg, const gchar *name_before, 
		const gchar *name, MsimMessageType type, gpointer data)
{
	MsimMessageElement *new_elem;
	GList *node_before;

	new_elem = msim_msg_element_new(name, type, data);	

	node_before = msim_msg_get_node(msg, name_before);

	return g_list_insert_before(msg, node_before, new_elem);
}

/** Pack a string using the given GFunc and seperator.
 * Used by msim_msg_dump() and msim_msg_pack().
 */
gchar *
msim_msg_pack_using(MsimMessage *msg, 
        GFunc gf, 
        const gchar *sep, 
		const gchar *begin, const gchar *end)
{
	gchar **strings;
	gchar **strings_tmp;
	gchar *joined;
	gchar *final;
	int i;

	g_return_val_if_fail(msg != NULL, NULL);

	/* Add one for NULL terminator for g_strjoinv(). */
	strings = (gchar **)g_new0(gchar *, g_list_length(msg) + 1);

	strings_tmp = strings;
	g_list_foreach(msg, gf, &strings_tmp);

	joined = g_strjoinv(sep, strings);
	final = g_strconcat(begin, joined, end, NULL);
	g_free(joined);

	/* Clean up. */
	for (i = 0; i < g_list_length(msg); ++i)
	{
		g_free(strings[i]);
	}

	g_free(strings);

	return final;
}
/** Store a human-readable string describing the element.
 *
 * @param data Pointer to an MsimMessageElement.
 * @param user_data 
 */
static void 
msim_msg_debug_string_element(gpointer data, gpointer user_data)
{
	MsimMessageElement *elem;
	gchar *string;
	GString *gs;
	gchar *binary;
	gchar ***items;	 	/* wow, a pointer to a pointer to a pointer */

	elem = (MsimMessageElement *)data;
	items = user_data;

	switch (elem->type)
	{
		case MSIM_TYPE_INTEGER:
			string = g_strdup_printf("%s(integer): %d", elem->name, 
                    GPOINTER_TO_UINT(elem->data));
			break;

		case MSIM_TYPE_RAW:
			string = g_strdup_printf("%s(raw): %s", elem->name, 
                    elem->data ? (gchar *)elem->data : "(NULL)");
			break;

		case MSIM_TYPE_STRING:
			string = g_strdup_printf("%s(string): %s", elem->name, 
                    elem->data ? (gchar *)elem->data : "(NULL)");
			break;

		case MSIM_TYPE_BINARY:
			gs = (GString *)elem->data;
			binary = purple_base64_encode((guchar*)gs->str, gs->len);
			string = g_strdup_printf("%s(binary, %d bytes): %s", elem->name, (int)gs->len, binary);
			g_free(binary);
			break;

		case MSIM_TYPE_BOOLEAN:
			string = g_strdup_printf("%s(boolean): %s", elem->name,
					elem->data ? "TRUE" : "FALSE");
			break;

		case MSIM_TYPE_DICTIONARY:
            {
                gchar *s;

                if (!elem->data)
                    s = g_strdup("(NULL)");
                else
                    s = msim_msg_dump_to_str((MsimMessage *)elem->data);

                if (!s)
                    s = g_strdup("(NULL, couldn't msim_msg_dump_to_str)");

                string = g_strdup_printf("%s(dict): %s", elem->name, s);

                g_free(s);
            }
			break;
			
		case MSIM_TYPE_LIST:
            {
                GString *gs;
                GList *gl;
                guint i;

                gs = g_string_new("");
                g_string_append_printf(gs, "%s(list): \n", elem->name);

                i = 0;
                for (gl = (GList *)elem->data; gl != NULL; gl = g_list_next(gl))
                {
                    g_string_append_printf(gs, " %d. %s\n", i, (gchar *)(gl->data));
                    ++i;
                }
                
                string = gs->str;
            }
			break;

		default:
			string = g_strdup_printf("%s(unknown type %d", 
                    elem->name ? elem->name : "(NULL)", elem->type);
			break;
	}

	**items = string;
	++(*items);
}

/** Print a human-readable string of the message to Purple's debug log.
 *
 * @param fmt_string A static string, in which '%s' will be replaced.
 */
void 
msim_msg_dump(const gchar *fmt_string, MsimMessage *msg)
{
    gchar *debug_str;

    g_return_if_fail(fmt_string != NULL);

    debug_str = msim_msg_dump_to_str(msg);
    
    g_return_if_fail(debug_str != NULL);

    purple_debug_info("msim_msg_dump", "debug_str=%s\n", debug_str);


	purple_debug_info("msim", fmt_string, debug_str);

	g_free(debug_str);
}

/** Return a human-readable string of the message.
 *
 * @return A new gchar *, must be g_free()'d.
 */
gchar *
msim_msg_dump_to_str(MsimMessage *msg)
{
	gchar *debug_str;

	if (!msg)
	{
		debug_str = g_strdup("<MsimMessage: empty>");
	} else {
		debug_str = msim_msg_pack_using(msg, msim_msg_debug_string_element, 
				"\n", "<MsimMessage: \n", "\n/MsimMessage>");
	}

    return debug_str;
}

/** Return a message element data as a new string for a raw protocol message, converting from other types (integer, etc.) if necessary.
 *
 * @return const gchar * The data as a string, or NULL. Caller must g_free().
 *
 * Returns a string suitable for inclusion in a raw protocol message, not necessarily
 * optimal for human consumption. For example, strings are escaped. Use 
 * msim_msg_get_string() if you want a string, which in some cases is same as this.
 */
static gchar *
msim_msg_pack_element_data(MsimMessageElement *elem)
{
    g_return_val_if_fail(elem != NULL, NULL);

	switch (elem->type)
	{
		case MSIM_TYPE_INTEGER:
			return g_strdup_printf("%d", GPOINTER_TO_UINT(elem->data));

		case MSIM_TYPE_RAW:
			/* Not un-escaped - this is a raw element, already escaped if necessary. */
			return (gchar *)g_strdup((gchar *)elem->data);

		case MSIM_TYPE_STRING:
			/* Strings get escaped. msim_escape() creates a new string. */
            g_return_val_if_fail(elem->data != NULL, NULL);
			return elem->data ? msim_escape((gchar *)elem->data) :
                g_strdup("(NULL)");

		case MSIM_TYPE_BINARY:
			{
				GString *gs;

				gs = (GString *)elem->data;
				/* Do not escape! */
				return purple_base64_encode((guchar *)gs->str, gs->len);
			}

		case MSIM_TYPE_BOOLEAN:
			/* Not used by messages in the wire protocol * -- see msim_msg_pack_element.
             * Only used by dictionaries, see msim_msg_pack_element_dict. */
            return elem->data ? g_strdup("On") : g_strdup("Off");

		case MSIM_TYPE_DICTIONARY:
			/* TODO: pack using k=v\034k2=v2\034... */
			return msim_msg_pack_dict((MsimMessage *)elem->data);
			
		case MSIM_TYPE_LIST:
			/* Pack using a|b|c|d|... */
            {
                GString *gs;
                GList *gl;

                gs = g_string_new("");

                for (gl = (GList *)elem->data; gl != NULL; gl = g_list_next(gl))
                {
                    g_string_append_printf(gs, "%s|", (gchar*)(gl->data));
                }
                
                return gs->str;
            }

		default:
			purple_debug_info("msim", "field %s, unknown type %d\n", 
                    elem->name ? elem->name : "(NULL)", 
                    elem->type);
			return NULL;
	}
}

/** Pack an element into its protcol representation inside a dictionary.
 *
 * See msim_msg_pack_element().
 */
static void
msim_msg_pack_element_dict(gpointer data, gpointer user_data)
{
    MsimMessageElement *elem;
    gchar *string, *data_string, ***items;

    elem = (MsimMessageElement *)data;
    items = (gchar ***)user_data;

	/* Exclude elements beginning with '_' from packed protocol messages. */
	if (elem->name[0] == '_')
	{
		return;
	}

	data_string = msim_msg_pack_element_data(elem);

    g_return_if_fail(data_string != NULL);

	switch (elem->type)
	{
		/* These types are represented by key name/value pairs (converted above). */
		case MSIM_TYPE_INTEGER:
		case MSIM_TYPE_RAW:
		case MSIM_TYPE_STRING:
		case MSIM_TYPE_BINARY:
		case MSIM_TYPE_DICTIONARY:
		case MSIM_TYPE_LIST:
		case MSIM_TYPE_BOOLEAN:     /* Boolean is On or Off */
			string = g_strconcat(elem->name, "=", data_string, NULL);
			break;

		default:
			g_free(data_string);
			g_return_if_fail(FALSE);
			break;
	}

	g_free(data_string);

	**items = string;
	++(*items);
}

/** Pack an element into its protocol representation. 
 *
 * @param data Pointer to an MsimMessageElement.
 * @param user_data Pointer to a gchar ** array of string items.
 *
 * Called by msim_msg_pack(). Will pack the MsimMessageElement into
 * a part of the protocol string and append it to the array. Caller
 * is responsible for creating array to correct dimensions, and
 * freeing each string element of the array added by this function.
 */
static void 
msim_msg_pack_element(gpointer data, gpointer user_data)
{
	MsimMessageElement *elem;
	gchar *string, *data_string;
	gchar ***items;

	elem = (MsimMessageElement *)data;
	items = (gchar ***)user_data;

	/* Exclude elements beginning with '_' from packed protocol messages. */
	if (elem->name[0] == '_')
	{
		return;
	}

	data_string = msim_msg_pack_element_data(elem);

	switch (elem->type)
	{
		/* These types are represented by key name/value pairs (converted above). */
		case MSIM_TYPE_INTEGER:
		case MSIM_TYPE_RAW:
		case MSIM_TYPE_STRING:
		case MSIM_TYPE_BINARY:
		case MSIM_TYPE_DICTIONARY:
		case MSIM_TYPE_LIST:
			string = g_strconcat(elem->name, "\\", data_string, NULL);
			break;

		/* Boolean is represented by absence or presence of name. */
		case MSIM_TYPE_BOOLEAN:
			if (GPOINTER_TO_UINT(elem->data))
			{
				/* True - leave in, with blank value. */
				string = g_strdup_printf("%s\\", elem->name);
			} else {
				/* False - leave out. */
				string = g_strdup("");
			}
			break;

		default:
			g_free(data_string);
			g_return_if_fail(FALSE);
			break;
	}

	g_free(data_string);

	**items = string;
	++(*items);
}


/** Return a packed string of a message suitable for sending over the wire.
 *
 * @return A string. Caller must g_free().
 */
gchar *
msim_msg_pack(MsimMessage *msg)
{
	g_return_val_if_fail(msg != NULL, NULL);

	return msim_msg_pack_using(msg, msim_msg_pack_element, "\\", "\\", "\\final\\");
}

/** Return a packed string of a dictionary, suitable for embedding in MSIM_TYPE_DICTIONARY.
 *
 * @return A string; caller must g_free().
 */
gchar *
msim_msg_pack_dict(MsimMessage *msg)
{
    g_return_val_if_fail(msg != NULL, NULL);

    return msim_msg_pack_using(msg, msim_msg_pack_element_dict, "\034", "", "");
}

/** 
 * Parse a raw protocol message string into a MsimMessage *.
 *
 * @param raw The raw message string to parse, will be g_free()'d.
 *
 * @return MsimMessage *. Caller should msim_msg_free() when done.
 */
MsimMessage *
msim_parse(gchar *raw)
{
	MsimMessage *msg;
    gchar *token;
    gchar **tokens;
    gchar *key;
    gchar *value;
    int i;

    g_return_val_if_fail(raw != NULL, NULL);

    purple_debug_info("msim", "msim_parse: got <%s>\n", raw);

    key = NULL;

    /* All messages begin with a \. */
    if (raw[0] != '\\' || raw[1] == 0)
    {
        purple_debug_info("msim", "msim_parse: incomplete/bad string, "
                "missing initial backslash: <%s>\n", raw);
        /* XXX: Should we try to recover, and read to first backslash? */

        g_free(raw);
        return NULL;
    }

    msg = msim_msg_new(FALSE);

    for (tokens = g_strsplit(raw + 1, "\\", 0), i = 0; 
            (token = tokens[i]);
            i++)
    {
#ifdef MSIM_DEBUG_PARSE
        purple_debug_info("msim", "tok=<%s>, i%2=%d\n", token, i % 2);
#endif
        if (i % 2)
        {
			/* Odd-numbered ordinal is a value. */

			value = token;
		
			/* Incoming protocol messages get tagged as MSIM_TYPE_RAW, which
			 * represents an untyped piece of data. msim_msg_get_* will
			 * convert to appropriate types for caller, and handle unescaping if needed. */
			msg = msim_msg_append(msg, g_strdup(key), MSIM_TYPE_RAW, g_strdup(value));
#ifdef MSIM_DEBUG_PARSE
			purple_debug_info("msim", "insert string: |%s|=|%s|\n", key, value);
#endif
        } else {
			/* Even numbered indexes are key names. */
            key = token;
        }
    }
    g_strfreev(tokens);

    /* Can free now since all data was copied to hash key/values */
    g_free(raw);

    return msg;
}

/**
 * Parse a \x1c-separated "dictionary" of key=value pairs into a hash table.
 *
 * @param body_str The text of the dictionary to parse. Often the
 *                  value for the 'body' field.
 *
 * @return Hash table of the keys and values. Must g_hash_table_destroy() when done.
 */
GHashTable *
msim_parse_body(const gchar *body_str)
{
    GHashTable *table;
    gchar *item;
    gchar **items;
    gchar **elements;
    guint i;

    g_return_val_if_fail(body_str != NULL, NULL);

    table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
 
    for (items = g_strsplit(body_str, "\x1c", 0), i = 0; 
        (item = items[i]);
        i++)
    {
        gchar *key, *value;

        elements = g_strsplit(item, "=", 2);

        key = elements[0];
        if (!key)
        {
            purple_debug_info("msim", "msim_parse_body(%s): null key\n", 
					body_str);
            g_strfreev(elements);
            break;
        }

        value = elements[1];
        if (!value)
        {
            purple_debug_info("msim", "msim_parse_body(%s): null value\n", 
					body_str);
            g_strfreev(elements);
            break;
        }

#ifdef MSIM_DEBUG_PARSE
        purple_debug_info("msim", "-- %s: %s\n", key ? key : "(NULL)", 
                value ? value : "(NULL)");
#endif

        /* XXX: This overwrites duplicates. */
        /* TODO: make the GHashTable values be GList's, and append to the list if 
         * there is already a value of the same key name. This is important for
         * the WebChallenge message. */
        g_hash_table_insert(table, g_strdup(key), g_strdup(value));
        
        g_strfreev(elements);
    }

    g_strfreev(items);

    return table;
}

/** Search for and return the node in msg, matching name, or NULL. 
 *
 * @param msg Message to search within.
 * @param name Field name to search for.
 *
 * @return The GList * node for the MsimMessageElement with the given name, or NULL if not found or name is NULL.
 *
 * For internal use - users probably want to use msim_msg_get() to
 * access the MsimMessageElement *, instead of the GList * container.
 *
 */
static GList *
msim_msg_get_node(MsimMessage *msg, const gchar *name)
{
	GList *i;

	if (!name)
	{
		return NULL;
	}

	/* Linear search for the given name. O(n) but n is small. */
	for (i = g_list_first(msg); i != NULL; i = g_list_next(i))
	{
		MsimMessageElement *elem;

		elem = i->data;
		g_return_val_if_fail(elem != NULL, NULL);

		if (strcmp(elem->name, name) == 0)
			return i;
	}
	return NULL;
}

/** Return the first MsimMessageElement * with given name in the MsimMessage *. 
 *
 * @param name Name to search for.
 *
 * @return MsimMessageElement * matching name, or NULL.
 *
 * Note: useful fields of MsimMessageElement are 'data' and 'type', which
 * you can access directly. But it is often more convenient to use
 * another msim_msg_get_* that converts the data to what type you want.
 */
MsimMessageElement *
msim_msg_get(MsimMessage *msg, const gchar *name)
{
	GList *node;

	node = msim_msg_get_node(msg, name);
	if (node)
		return (MsimMessageElement *)node->data;
	else
		return NULL;
}

/** Return the data of an element of a given name, as a string.
 *
 * @param name Name of element.
 *
 * @return gchar * The data as a string. Caller must g_free().
 *
 * Note that msim_msg_pack_element_data() is similar, but returns a string
 * for inclusion into a raw protocol string (escaped and everything).
 * This function unescapes the string for you, if needed.
 */
gchar *
msim_msg_get_string(MsimMessage *msg, const gchar *name)
{
	MsimMessageElement *elem;

	elem = msim_msg_get(msg, name);
    g_return_val_if_fail(elem != NULL , NULL);

	switch (elem->type)
	{
		case MSIM_TYPE_INTEGER:
			return g_strdup_printf("%d", GPOINTER_TO_UINT(elem->data));

		case MSIM_TYPE_RAW:
			/* Raw element from incoming message - if its a string, it'll
			 * be escaped. */
			return msim_unescape((gchar *)elem->data);

		case MSIM_TYPE_STRING:
			/* Already unescaped. */
			return g_strdup((gchar *)elem->data);

		default:
			purple_debug_info("msim", "msim_msg_get_string: type %d unknown, name %s\n",
					elem->type, name ? name : "(NULL)");
			return NULL;
	}
}

/** Return an element as a new list. Caller frees with msim_msg_list_free(). */
GList *
msim_msg_get_list(MsimMessage *msg, const gchar *name)
{
    MsimMessageElement *elem;

    elem = msim_msg_get(msg, name);
    if (!elem)
        return NULL;

    switch (elem->type)
    {
        case MSIM_TYPE_LIST:
            return msim_msg_list_copy((GList *)elem->data);

        case MSIM_TYPE_RAW:
            return msim_msg_list_parse((gchar *)elem->data);

        default:
            purple_debug_info("msim_msg_get_list", "type %d unknown, name %s\n",
                    elem->type, name ? name : "(NULL)");
            return NULL;
    }
}

/** Parse a \034-deliminated and =-separated string into a dictionary. TODO */
MsimMessage *
msim_msg_dictionary_parse(gchar *raw)
{
    /* TODO - get code from msim_parse_body, but parse into MsimMessage */
    return NULL;
}

/** Return an element as a new dictionary. Caller frees with msim_msg_free(). */
MsimMessage *
msim_msg_get_dictionary(MsimMessage *msg, const gchar *name)
{
    MsimMessageElement *elem;

    elem = msim_msg_get(msg, name);
    if (!elem)
        return NULL;

    switch (elem->type)
    {
        case MSIM_TYPE_DICTIONARY:
            return msim_msg_clone((MsimMessage *)elem->data);
        
        case MSIM_TYPE_RAW:
            return msim_msg_dictionary_parse((gchar *)elem->data);

        default:
            purple_debug_info("msim_msg_get_dictionary", "type %d unknown, name %s\n",
                    elem->type, name ? name : "(NULL)");
            return NULL;
    }
}

/** Return the data of an element of a given name, as an integer.
 *
 * @param name Name of element.
 *
 * @return guint Numeric representation of data, or 0 if could not be converted / not found.
 *
 * Useful to obtain an element's data if you know it should be an integer,
 * even if it is not stored as an MSIM_TYPE_INTEGER. MSIM_TYPE_STRING will
 * be converted handled correctly, for example.
 */
guint 
msim_msg_get_integer(MsimMessage *msg, const gchar *name)
{
	MsimMessageElement *elem;

	elem = msim_msg_get(msg, name);

	if (!elem)
		return 0;

	switch (elem->type)
	{
		case MSIM_TYPE_INTEGER:
			return GPOINTER_TO_UINT(elem->data);

		case MSIM_TYPE_RAW:
		case MSIM_TYPE_STRING:
			/* TODO: find out if we need larger integers */
			return (guint)atoi((gchar *)elem->data);

		default:
			return 0;
	}
}

/** Return the data of an element of a given name, as a binary GString.
 *
 * @param binary_data A pointer to a new pointer, which will be filled in with the binary data. CALLER MUST g_free().
 *
 * @param binary_length A pointer to an integer, which will be set to the binary data length.
 *
 * @return TRUE if successful, FALSE if not.
 */
gboolean 
msim_msg_get_binary(MsimMessage *msg, const gchar *name, 
		gchar **binary_data, gsize *binary_length)
{
	MsimMessageElement *elem;

	elem = msim_msg_get(msg, name);
    if (!elem)
        return FALSE;

	switch (elem->type)
	{
		case MSIM_TYPE_RAW:
			 /* Incoming messages are tagged with MSIM_TYPE_RAW, and
			 * converted appropriately. They can still be "strings", just they won't
			 * be tagged as MSIM_TYPE_STRING (as MSIM_TYPE_STRING is intended to be used
			 * by msimprpl code for things like instant messages - stuff that should be
			 * escaped if needed). DWIM.
			 */
	
			/* Previously, incoming messages were stored as MSIM_TYPE_STRING.
			 * This was fine for integers and strings, since they can easily be
			 * converted in msim_get_*, as desirable. However, it does not work
			 * well for binary strings. Consider:
			 *
			 * If incoming base64'd elements were tagged as MSIM_TYPE_STRING.
			 * msim_msg_get_binary() sees MSIM_TYPE_STRING, base64 decodes, returns.
			 * everything is fine.
			 * But then, msim_send() is called on the incoming message, which has
			 * a base64'd MSIM_TYPE_STRING that really is encoded binary. The values
			 * will be escaped since strings are escaped, and / becomes /2; no good.
			 *
			 */
			*binary_data = (gchar *)purple_base64_decode((const gchar *)elem->data, binary_length);
			return TRUE;

		case MSIM_TYPE_BINARY:
			{
				GString *gs;

				gs = (GString *)elem->data;

				/* Duplicate data, so caller can g_free() it. */
				*binary_data = g_new0(char, gs->len);
				memcpy(*binary_data, gs->str, gs->len);

				*binary_length = gs->len;

				return TRUE;
			}


			/* Rejected because if it isn't already a GString, have to g_new0 it and
			 * then caller has to ALSO free the GString! 
			 *
			 * return (GString *)elem->data; */

		default:
			purple_debug_info("msim", "msim_msg_get_binary: unhandled type %d for key %s\n",
					elem->type, name ? name : "(NULL)");
			return FALSE;
	}
}