pidgin/pidgin

More error logging, please.
release-2.x.y
2014-01-18, Mark Doliner
956f247148db
More error logging, please.
/** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include "myspace.h"
#include "message.h"
static void msim_msg_debug_string_element(gpointer data, gpointer user_data);
/**
* Escape codes and associated replacement text, used for protocol message
* escaping and unescaping.
*/
static struct MSIM_ESCAPE_REPLACEMENT {
gchar *code;
gchar text;
} msim_escape_replacements[] = {
{ "/1", '/' },
{ "/2", '\\' },
/* { "/3", "|" }, */ /* Not used here -- only for within arrays */
{ NULL, 0 }
};
/**
* Escape a protocol message.
*
* @return The escaped message. Caller must g_free().
*/
gchar *
msim_escape(const gchar *msg)
{
GString *gs;
guint i, j;
guint msg_len;
gs = g_string_new("");
msg_len = strlen(msg);
for (i = 0; i < msg_len; ++i) {
struct MSIM_ESCAPE_REPLACEMENT *replacement;
gchar *replace;
replace = NULL;
/* Check for characters that need to be escaped, and escape them. */
for (j = 0; (replacement = &msim_escape_replacements[j]) &&
replacement->code != NULL; ++j) {
if (msg[i] == replacement->text) {
replace = replacement->code;
break;
}
}
if (replace) {
g_string_append(gs, replace);
} else {
g_string_append_c(gs, msg[i]);
}
}
#ifdef MSIM_DEBUG_ESCAPE
purple_debug_info("msim", "msim_escape: msg=%s, ret=%s\n", msg, gs->str);
#endif
return g_string_free(gs, FALSE);
}
/**
* Unescape a protocol message.
*
* @return The unescaped message, caller must g_free().
*/
gchar *
msim_unescape(const gchar *msg)
{
GString *gs;
guint i, j;
guint msg_len;
gs = g_string_new("");
msg_len = strlen(msg);
for (i = 0; i < msg_len; ++i) {
struct MSIM_ESCAPE_REPLACEMENT *replacement;
gchar replace;
replace = msg[i];
for (j = 0; (replacement = &msim_escape_replacements[j]) &&
replacement->code != NULL; ++j) {
if (msg[i] == replacement->code[0] &&
i + 1 < msg_len &&
msg[i + 1] == replacement->code[1]) {
replace = replacement->text;
++i;
break;
}
}
g_string_append_c(gs, replace);
}
#ifdef MSIM_DEBUG_ESCAPE
purple_debug_info("msim", "msim_unescape: msg=%s, ret=%s\n", msg, gs->str);
#endif
return g_string_free(gs, FALSE);
}
/**
* Create a new message from va_list and its first argument.
*
* @param first_key The first argument (a key), or NULL to take all arguments
* from argp.
* @param argp A va_list of variadic arguments, already started with va_start().
* @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(gchar *first_key, va_list argp)
{
gchar *key, *value;
MsimMessageType type;
MsimMessage *msg;
gboolean first;
GString *gs;
GList *gl;
MsimMessage *dict;
/* Begin with an empty message. */
msg = NULL;
/* First parameter can be given explicitly. */
first = first_key != NULL;
/* Read key, type, value triplets until NULL. */
do {
if (first) {
key = first_key;
first = FALSE;
} else {
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:
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:
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:
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);
return msg;
}
/**
* Create a new MsimMessage.
*
* @param first_key The first key in the sequence, or NULL for an empty message.
* @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(gchar *first_key, ...)
{
MsimMessage *ret = NULL;
va_list argp;
if (first_key) {
va_start(argp, first_key);
ret = msim_msg_new_v(first_key, argp);
va_end(argp);
}
return ret;
}
/**
* Pack a string using the given GFunc and seperator.
* Used by msim_msg_dump() and msim_msg_pack().
*/
static gchar *
msim_msg_pack_using(MsimMessage *msg,
GFunc gf,
const gchar *sep,
const gchar *begin, const gchar *end)
{
int num_items;
gchar **strings;
gchar **strings_tmp;
gchar *joined;
gchar *final;
int i;
g_return_val_if_fail(msg != NULL, NULL);
num_items = g_list_length(msg);
/* Add one for NULL terminator for g_strjoinv(). */
strings = (gchar **)g_new0(gchar *, num_items + 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 < num_items; ++i) {
g_free(strings[i]);
}
g_free(strings);
return final;
}
/**
* Return a human-readable string of the message.
*
* @return A new gchar *, must be g_free()'d.
*/
static 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;
}
/**
* 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 */
gchar *s;
GList *gl;
guint i;
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:
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:
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 = g_string_free(gs, FALSE);
break;
default:
string = g_strdup_printf("%s(unknown type %d",
elem->name ? elem->name : "(NULL)", elem->type);
break;
}
**items = string;
++(*items);
}
/**
* 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(const MsimMessage *msg, const gchar *name)
{
GList *node;
if (!name || !msg) {
return NULL;
}
/* Linear search for the given name. O(n) but n is small. */
for (node = (GList*)msg; node != NULL; node = g_list_next(node)) {
MsimMessageElement *elem;
elem = (MsimMessageElement *)node->data;
g_return_val_if_fail(elem != NULL, NULL);
g_return_val_if_fail(elem->name != NULL, NULL);
if (strcmp(elem->name, name) == 0) {
return node;
}
}
return NULL;
}
/**
* Create a new MsimMessageElement * - must be g_free()'d.
*
* For internal use; users probably want msim_msg_append() or msim_msg_insert_before().
*
* @param dynamic_name Whether 'name' should be freed when the message is destroyed.
*/
static MsimMessageElement *
msim_msg_element_new(const gchar *name, MsimMessageType type, gpointer data, gboolean dynamic_name)
{
MsimMessageElement *elem;
elem = g_new0(MsimMessageElement, 1);
elem->name = name;
elem->dynamic_name = dynamic_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, FALSE));
}
/**
* Append a new element, but with a dynamically-allocated name.
* Exactly the same as msim_msg_append(), except 'name' will be freed when
* the message is destroyed. Normally, it isn't, because a static string is given.
*/
static MsimMessage *
msim_msg_append_dynamic_name(MsimMessage *msg, gchar *name,
MsimMessageType type, gpointer data)
{
return g_list_append(msg, msim_msg_element_new(name, type, data, TRUE));
}
/**
* 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, FALSE);
node_before = msim_msg_get_node(msg, name_before);
return g_list_insert_before(msg, node_before, new_elem);
}
/**
* Perform a deep copy on a GList * of gchar * strings. Free with msim_msg_list_free().
*/
static GList *
msim_msg_list_copy(const 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;
}
/**
* 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;
GString *gs;
MsimMessage *dict;
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:
gs = (GString *)elem->data;
new_data = g_string_new_len(gs->str, gs->len);
break;
case MSIM_TYPE_DICTIONARY:
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_reached();
}
/* Append cloned data. Note that the 'name' field is a static string, so it
* never needs to be copied nor freed. */
if (elem->dynamic_name)
*new = msim_msg_append_dynamic_name(*new, g_strdup(elem->name), elem->type, new_data);
else
*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 the data of a message element.
*
* @param elem The MsimMessageElement *
*
* Note this only frees the element data; you may also want to free the
* element itself with g_free() (see msim_msg_free_element()).
*/
void
msim_msg_free_element_data(MsimMessageElement *elem)
{
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_data: "
"not freeing unknown type %d\n", elem->type);
break;
}
}
/**
* Free a GList * of MsimMessageElement *'s.
*/
void
msim_msg_list_free(GList *l)
{
for (; l != NULL; l = g_list_next(l)) {
MsimMessageElement *elem;
elem = (MsimMessageElement *)l->data;
/* Note that name is almost never dynamically allocated elsewhere;
* it is usually a static string, but not in lists. So cast it. */
g_free((gchar *)elem->name);
g_free(elem->data);
g_free(elem);
}
g_list_free(l);
}
/**
* Free an individual message element.
*
* @param data MsimMessageElement * to free.
* @param user_data Not used; required to match g_list_foreach() callback prototype.
*
* Frees both the element data and the element itself.
* Also frees the name if dynamic_name is TRUE.
*/
static void
msim_msg_free_element(gpointer data, gpointer user_data)
{
MsimMessageElement *elem;
elem = (MsimMessageElement *)data;
msim_msg_free_element_data(elem);
if (elem->dynamic_name)
/* Need to cast to remove const-ness, because
* elem->name is almost always a constant, static
* string, but not in this case. */
g_free((gchar *)elem->name);
g_free(elem);
}
/**
* Free a complete message.
*/
void
msim_msg_free(MsimMessage *msg)
{
if (!msg) {
/* already free as can be */
return;
}
g_list_foreach(msg, msim_msg_free_element, NULL);
g_list_free(msg);
}
/**
* 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_reached();
break;
}
g_free(data_string);
**items = string;
++(*items);
}
/**
* 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);
}
/**
* 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().
*/
static 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", "", "");
}
/**
* 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);
return success;
}
/**
* 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.
*/
gchar *
msim_msg_pack_element_data(MsimMessageElement *elem)
{
GString *gs;
GList *gl;
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:
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:
return msim_msg_pack_dict((MsimMessage *)elem->data);
case MSIM_TYPE_LIST:
/* Pack using a|b|c|d|... */
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));
/* All but last element is separated by a bar. */
if (g_list_next(gl))
g_string_append(gs, "|");
}
return g_string_free(gs, FALSE);
default:
purple_debug_info("msim", "field %s, unknown type %d\n",
elem->name ? elem->name : "(NULL)",
elem->type);
return NULL;
}
}
/**
* 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(NULL, argp);
va_end(argp);
/* Actually send the message. */
success = msim_msg_send(session, msg);
/* Cleanup. */
msim_msg_free(msg);
return success;
}
/**
* 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", fmt_string, debug_str);
g_free(debug_str);
}
/**
* 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(const 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? */
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_dynamic_name(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);
return msg;
}
/**
* 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(const MsimMessage *msg, const gchar *name)
{
GList *node;
node = msim_msg_get_node(msg, name);
if (node) {
return (MsimMessageElement *)node->data;
} else {
return NULL;
}
}
gchar *
msim_msg_get_string_from_element(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:
/* 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_element: type %d unknown, name %s\n",
elem->type, elem->name ? elem->name : "(NULL)");
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, or NULL if not found.
* 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(const MsimMessage *msg, const gchar *name)
{
MsimMessageElement *elem;
elem = msim_msg_get(msg, name);
if (!elem) {
return NULL;
}
return msim_msg_get_string_from_element(elem);
}
/**
* Parse a |-separated string into a new GList. Free with msim_msg_list_free().
*/
static GList *
msim_msg_list_parse(const gchar *raw)
{
gchar **array;
GList *list;
guint i;
array = g_strsplit(raw, "|", 0);
list = NULL;
/* TODO: escape/unescape /3 <-> | within list elements */
for (i = 0; array[i] != NULL; ++i) {
MsimMessageElement *elem;
/* Freed in msim_msg_list_free() */
elem = g_new0(MsimMessageElement, 1);
/* Give the element a name for debugging purposes.
* Not supposed to be looked up by this name; instead,
* lookup the elements by indexing the array. */
elem->name = g_strdup_printf("(list item #%d)", i);
elem->type = MSIM_TYPE_RAW;
elem->data = g_strdup(array[i]);
list = g_list_append(list, elem);
}
g_strfreev(array);
return list;
}
static GList *
msim_msg_get_list_from_element(MsimMessageElement *elem)
{
g_return_val_if_fail(elem != NULL, 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, elem->name ? elem->name : "(NULL)");
return NULL;
}
}
/**
* Return an element as a new list. Caller frees with msim_msg_list_free().
*/
GList *
msim_msg_get_list(const MsimMessage *msg, const gchar *name)
{
MsimMessageElement *elem;
elem = msim_msg_get(msg, name);
if (!elem) {
return NULL;
}
return msim_msg_get_list_from_element(elem);
}
/**
* Parse a \x1c-separated "dictionary" of key=value pairs into a hash table.
*
* @param raw The text of the dictionary to parse. Often the
* value for the 'body' field.
*
* @return A new MsimMessage *. Must msim_msg_free() when done.
*/
static MsimMessage *
msim_msg_dictionary_parse(const gchar *raw)
{
MsimMessage *dict;
gchar *item;
gchar **items;
gchar **elements;
guint i;
g_return_val_if_fail(raw != NULL, NULL);
dict = msim_msg_new(NULL);
for (items = g_strsplit(raw, "\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_msg_dictionary_parse(%s): null key\n",
raw);
g_strfreev(elements);
break;
}
value = elements[1];
if (!value) {
purple_debug_info("msim", "msim_msg_dictionary_prase(%s): null value\n",
raw);
g_strfreev(elements);
break;
}
#ifdef MSIM_DEBUG_PARSE
purple_debug_info("msim_msg_dictionary_parse","-- %s: %s\n", key ? key : "(NULL)",
value ? value : "(NULL)");
#endif
/* Append with _dynamic_name since g_strdup(key) is dynamic, and
* needs to be freed when the message is destroyed. It isn't static as usual. */
dict = msim_msg_append_dynamic_name(dict, g_strdup(key), MSIM_TYPE_RAW, g_strdup(value));
g_strfreev(elements);
}
g_strfreev(items);
return dict;
}
static MsimMessage *
msim_msg_get_dictionary_from_element(MsimMessageElement *elem)
{
g_return_val_if_fail(elem != NULL, NULL);
switch (elem->type) {
case MSIM_TYPE_DICTIONARY:
return msim_msg_clone((MsimMessage *)elem->data);
case MSIM_TYPE_RAW:
return msim_msg_dictionary_parse(elem->data);
default:
purple_debug_info("msim_msg_get_dictionary", "type %d unknown, name %s\n",
elem->type, elem->name ? elem->name : "(NULL)");
return NULL;
}
}
/**
* Return an element as a new dictionary. Caller frees with msim_msg_free().
*/
MsimMessage *
msim_msg_get_dictionary(const MsimMessage *msg, const gchar *name)
{
MsimMessageElement *elem;
elem = msim_msg_get(msg, name);
if (!elem) {
return NULL;
}
return msim_msg_get_dictionary_from_element(elem);
}
guint
msim_msg_get_integer_from_element(MsimMessageElement *elem)
{
g_return_val_if_fail(elem != NULL, 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 an unsigned 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(const MsimMessage *msg, const gchar *name)
{
MsimMessageElement *elem;
elem = msim_msg_get(msg, name);
if (!elem) {
return 0;
}
return msim_msg_get_integer_from_element(elem);
}
static gboolean
msim_msg_get_binary_from_element(MsimMessageElement *elem, gchar **binary_data, gsize *binary_length)
{
GString *gs;
g_return_val_if_fail(elem != NULL, 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 ((*binary_data) != NULL);
case MSIM_TYPE_BINARY:
gs = (GString *)elem->data;
/* Duplicate data, so caller can g_free() it. */
*binary_data = g_memdup(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, elem->name ? elem->name : "(NULL)");
return FALSE;
}
}
/**
* 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(const MsimMessage *msg, const gchar *name,
gchar **binary_data, gsize *binary_length)
{
MsimMessageElement *elem;
elem = msim_msg_get(msg, name);
if (!elem) {
return FALSE;
}
return msim_msg_get_binary_from_element(elem, binary_data, binary_length);
}