Mercurial > pidgin
view libpurple/protocols/myspace/message.c @ 32819:2c6510167895 default tip
propagate from branch 'im.pidgin.pidgin.2.x.y' (head 3315c5dfbd0ad16511bdcf865e5b07c02d07df24)
to branch 'im.pidgin.pidgin' (head cbd1eda6bcbf0565ae7766396bb8f6f419cb6a9a)
author | Elliott Sales de Andrade <qulogic@pidgin.im> |
---|---|
date | Sat, 02 Jun 2012 02:30:49 +0000 |
parents | 943fce8ef142 |
children |
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., 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(). 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(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); va_end(argp); 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, ...) { va_list argp; if (first_key) { va_start(argp, first_key); return msim_msg_new_v(first_key, argp); } else { return NULL; } } /** * 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); /* 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); }