Mercurial > pidgin.yaz
diff libpurple/protocols/mxit/markup.c @ 28903:69aa4660401a
Initial addition of the MXit protocol plugin, provided by the MXit folks
themselves.
author | John Bailey <rekkanoryo@rekkanoryo.org> |
---|---|
date | Sun, 08 Nov 2009 23:55:56 +0000 |
parents | |
children | 5f80ab7ac183 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/markup.c Sun Nov 08 23:55:56 2009 +0000 @@ -0,0 +1,1192 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- convert between MXit and libPurple markup -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#include "purple.h" + +#include "protocol.h" +#include "mxit.h" +#include "markup.h" +#include "chunk.h" +#include "formcmds.h" +#include "roster.h" + + +/* define this to enable emoticon (markup) debugging */ +#undef MXIT_DEBUG_EMO +/* define this to enable markup conversion debugging */ +#undef MXIT_DEBUG_MARKUP + + +#define MXIT_FRAME_MAGIC "MXF\x01" /* mxit emoticon magic number */ +#define MXIT_MAX_EMO_ID 16 /* maximum emoticon ID length */ +#define COLORCODE_LEN 6 /* colour code ID length */ + + +/* HTML tag types */ +#define MXIT_TAG_COLOR 0x01 /* font color tag */ +#define MXIT_TAG_SIZE 0x02 /* font size tag */ +#define MXIT_MAX_MSG_TAGS 90 /* maximum tags per message (pigdin hack work around) */ + +/* + * a HTML tag object + */ +struct tag { + char type; + char* value; +}; + + +#define MXIT_VIBE_MSG_COLOR "#9933FF" + +/* vibes */ +static const char* vibes[] = { + /* 0 */ "Cool Vibrations", + /* 1 */ "Purple Rain", + /* 2 */ "Polite", + /* 3 */ "Rock n Roll", + /* 4 */ "Summer Slumber", + /* 5 */ "Electric Razor", + /* 6 */ "S.O.S", + /* 7 */ "Jack Hammer", + /* 8 */ "Bumble Bee", + /* 9 */ "Ripple" +}; + + + +#ifdef MXIT_DEBUG_EMO +/*------------------------------------------------------------------------ + * Dump a byte buffer as hexadecimal to the console for debugging purposes. + * + * @param buf The data to dump + * @param len The length of the data + */ +static void hex_dump( const char* buf, int len ) +{ + char msg[256]; + int pos; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "Dumping data (%i bytes)\n", len ); + + memset( msg, 0x00, sizeof( msg ) ); + pos = 0; + + for ( i = 0; i < len; i++ ) { + + if ( pos == 0 ) + pos += sprintf( &msg[pos], "%04i: ", i ); + + pos += sprintf( &msg[pos], "0x%02X ", (unsigned char) buf[i] ); + + if ( i % 16 == 15 ) { + pos += sprintf( &msg[pos], "\n" ); + purple_debug_info( MXIT_PLUGIN_ID, msg ); + pos = 0; + } + else if ( i % 16 == 7 ) + pos += sprintf( &msg[pos], " " ); + } + + if ( pos > 0 ) { + pos += sprintf( &msg[pos], "\n" ); + purple_debug_info( MXIT_PLUGIN_ID, msg ); + pos = 0; + } +} +#endif + + +/*------------------------------------------------------------------------ + * Adds a link to a message + * + * @param mx The Markup message object + * @param linkname This is the what will be returned when the link gets clicked + * @param displayname This is the name for the link which will be displayed in the UI + */ +void mxit_add_html_link( struct RXMsgData* mx, const char* linkname, const char* displayname ) +{ +#ifdef MXIT_LINK_CLICK + char retstr[256]; + gchar* retstr64; + char link[256]; + int len; + + len = g_snprintf( retstr, sizeof( retstr ), "%s|%s|%s|%s|%s", MXIT_LINK_KEY, purple_account_get_username( mx->session->acc ), + purple_account_get_protocol_id( mx->session->acc ), mx->from, linkname ); + retstr64 = purple_base64_encode( (const unsigned char*) retstr, len ); + g_snprintf( link, sizeof( link ), "%s%s", MXIT_LINK_PREFIX, retstr64 ); + g_free( retstr64 ); + + g_string_append_printf( mx->msg, "<a href=\"%s\">%s</a>", link, displayname ); +#else + g_string_append_printf( mx->msg, "<b>%s</b>", linkname ); +#endif +} + + +/*------------------------------------------------------------------------ + * Extract an ASN.1 formatted length field from the data. + * + * @param data The source data + * @param size The extracted length + * @return The number of bytes extracted + */ +static unsigned int asn_getlength( const char* data, int* size ) +{ + unsigned int len = 0; + unsigned char bytes; + unsigned char byte; + int i; + + /* first byte specifies the number of bytes in the length */ + bytes = ( data[0] & ~0x80 ); + if ( bytes > sizeof( unsigned int ) ) { + /* file too big! */ + return -1; + } + data++; + + /* parse out the actual length */ + for ( i = 0; i < bytes; i++ ) { + byte = data[i]; + len <<= 8; + len += byte; + } + + *size = len; + return bytes + 1; +} + + +/*------------------------------------------------------------------------ + * Extract an ASN.1 formatted UTF-8 string field from the data. + * + * @param data The source data + * @param type Expected type of string + * @param utf8 The extracted string. Must be deallocated by caller. + * @return The number of bytes extracted + */ +static int asn_getUtf8( const char* data, char type, char** utf8 ) +{ + int len; + + /* validate the field type [1 byte] */ + if ( data[0] != type ) { + /* this is not a utf-8 string! */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid UTF-8 encoded string in ASN data (0x%02X)\n", (unsigned char) data[0] ); + return -1; + } + + len = data[1]; /* length field [1 bytes] */ + *utf8 = g_malloc( len + 1 ); + memcpy( *utf8, &data[2], len ); /* data field */ + (*utf8)[len] = '\0'; + + return ( len + 2 ); +} + + +/*------------------------------------------------------------------------ + * Free data associated with a Markup message object. + * + * @param mx The Markup message object + */ +static void free_markupdata( struct RXMsgData* mx ) +{ + if ( mx ) { + if ( mx->msg ) + g_string_free( mx->msg, TRUE ); + if ( mx->from ) + g_free( mx->from ); + g_free( mx ); + } +} + + +/*------------------------------------------------------------------------ + * Split the message into smaller messages and send them one at a time + * to pidgin to be displayed on the UI + * + * @param mx The received message object + */ +static void mxit_show_split_message( struct RXMsgData* mx ) +{ + const char* cont = "<font color=\"#999999\">continuing...</font>\n"; + GString* msg = NULL; + char* ch = NULL; + int pos = 0; + int start = 0; + int l_nl = 0; + int l_sp = 0; + int l_gt = 0; + int stop = 0; + int tags = 0; + int segs = 0; + gboolean intag = FALSE; + + /* + * awful hack to work around the awful hack in pidgin to work around GtkIMHtml's + * inefficient rendering of messages with lots of formatting changes. + * (reference: see the function pidgin_conv_write_conv() in gtkconv.c) the issue + * is that when you have more than 100 '<' characters in the message passed to + * pidgin, none of the markup (including links) are rendered and thus just dump + * all the text as is to the conversation window. this message dump is very + * confusing and makes it totally unusable. to work around this we will count + * the amount of tags and if its more than the pidgin threshold, we will just + * break the message up into smaller parts and send them seperately to pidgin. + * to the user it will look like multiple messages, but at least he will be able + * to use and understand it. + */ + + ch = mx->msg->str; + pos = start; + while ( ch[pos] ) { + + if ( ch[pos] == '<' ) { + tags++; + intag = TRUE; + } + else if ( ch[pos] == '\n' ) { + l_nl = pos; + } + else if ( ch[pos] == '>' ) { + l_gt = pos; + intag = FALSE; + } + else if ( ch[pos] == ' ' ) { + /* ignore spaces inside tags */ + if ( !intag ) + l_sp = pos; + } + else if ( ( ch[pos] == 'w' ) && ( pos + 4 < mx->msg->len ) && ( memcmp( &ch[pos], "www.", 4 ) == 0 ) ) { + tags += 2; + } + else if ( ( ch[pos] == 'h' ) && ( pos + 8 < mx->msg->len ) && ( memcmp( &ch[pos], "http://", 7 ) == 0 ) ) { + tags += 2; + } + + if ( tags > MXIT_MAX_MSG_TAGS ) { + /* we have reached the maximum amount of tags pidgin (gtk) can handle per message. + so its time to send what we have and then start building a new message */ + + /* now find the right place to break the message */ + if ( l_nl > start ) { + /* break at last '\n' char */ + stop = l_nl; + ch[stop] = '\0'; + msg = g_string_new( &ch[start] ); + ch[stop] = '\n'; + } + else if ( l_sp > start ) { + /* break at last ' ' char */ + stop = l_sp; + ch[stop] = '\0'; + msg = g_string_new( &ch[start] ); + ch[stop] = ' '; + } + else { + /* break at the last '>' char */ + char t; + stop = l_gt + 1; + t = ch[stop]; + ch[stop] = '\0'; + msg = g_string_new( &ch[start] ); + ch[stop] = t; + stop--; + } + + /* build the string */ + if ( segs ) + g_string_prepend( msg, cont ); + + /* push message to pidgin */ + serv_got_im( mx->session->con, mx->from, msg->str, mx->flags, mx->timestamp ); + g_string_free( msg, TRUE ); + msg = NULL; + + tags = 0; + segs++; + start = stop + 1; + } + + pos++; + } + + if ( start != pos ) { + /* send the last part of the message */ + + /* build the string */ + ch[pos] = '\0'; + msg = g_string_new( &ch[start] ); + ch[pos] = '\n'; + if ( segs ) + g_string_prepend( msg, cont ); + + /* push message to pidgin */ + serv_got_im( mx->session->con, mx->from, msg->str, mx->flags, mx->timestamp ); + g_string_free( msg, TRUE ); + msg = NULL; + } +} + + +/*------------------------------------------------------------------------ + * Insert custom emoticons and inline images into the message (if there + * are any), then give the message to the UI to display to the user. + * + * @param mx The received message object + */ +void mxit_show_message( struct RXMsgData* mx ) +{ + char* pos; + int start; + unsigned int end; + int emo_ofs; + char ii[128]; + char tag[64]; + int* img_id; + + if ( mx->got_img ) { + /* search and replace all emoticon tags with proper image tags */ + + while ( ( pos = strstr( mx->msg->str, MXIT_II_TAG ) ) != NULL ) { + start = pos - mx->msg->str; /* offset at which MXIT_II_TAG starts */ + emo_ofs = start + strlen( MXIT_II_TAG ); /* offset at which EMO's ID starts */ + end = emo_ofs + 1; /* offset at which MXIT_II_TAG ends */ + + while ( ( end < mx->msg->len ) && ( mx->msg->str[end] != '>' ) ) + end++; + + if ( end == mx->msg->len ) /* end of emoticon tag not found */ + break; + + memset( ii, 0x00, sizeof( ii ) ); + memcpy( ii, &mx->msg->str[emo_ofs], end - emo_ofs ); + + /* remove inline image tag */ + g_string_erase( mx->msg, start, ( end - start ) + 1 ); + + /* find the image entry */ + img_id = (int*) g_hash_table_lookup( mx->session->iimages, ii ); + if ( !img_id ) { + /* inline image not found, so we will just skip it */ + purple_debug_error( MXIT_PLUGIN_ID, "inline image NOT found (%s)\n", ii ); + } + else { + /* insert img tag */ + g_snprintf( tag, sizeof( tag ), "<img id=\"%i\">", *img_id ); + g_string_insert( mx->msg, start, tag ); + } + } + } + +#ifdef MXIT_DEBUG_MARKUP + purple_debug_info( MXIT_PLUGIN_ID, "Markup RX (converted): '%s'\n", mx->msg->str ); +#endif + + if ( mx->processed ) { + /* this message has already been taken care of, so just ignore it here */ + } + else if ( mx->chatid < 0 ) { + /* normal chat message */ + //serv_got_im( mx->session->con, mx->from, mx->msg->str, mx->flags, mx->timestamp ); + mxit_show_split_message( mx ); + } + else { + /* this is a multimx message */ + serv_got_chat_in( mx->session->con, mx->chatid, mx->from, mx->flags, mx->msg->str, mx->timestamp); + } + + /* freeup resource */ + free_markupdata( mx ); +} + + +/*------------------------------------------------------------------------ + * Extract the custom emoticon ID from the message. + * + * @param message The input data + * @param emid The extracted emoticon ID + */ +static void parse_emoticon_str( const char* message, char* emid ) +{ + int i; + + for ( i = 0; ( message[i] != '\0' && message[i] != '}' && i < MXIT_MAX_EMO_ID ); i++ ) { + emid[i] = message[i]; + } + + if ( message[i] == '\0' ) { + /* end of message reached, ignore the tag */ + emid[0] = '\0'; + } + else if ( i == MXIT_MAX_EMO_ID ) { + /* invalid tag length, ignore the tag */ + emid[0] = '\0'; + } + else + emid[i] = '\0'; +} + + +/*------------------------------------------------------------------------ + * Callback function invoked when a custom emoticon request to the WAP site completes. + * + * @param url_data + * @param user_data The Markup message object + * @param url_text The data returned from the WAP site + * @param len The length of the data returned + * @param error_message Descriptive error message + */ +static void emoticon_returned( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message ) +{ + struct RXMsgData* mx = (struct RXMsgData*) user_data; + const char* data = url_text; + unsigned int pos = 0; + char emo[16]; + int id; + char* str; + int em_size = 0; + char* em_data = NULL; + char* em_id = NULL; + int* intptr = NULL; + int res; + +#ifdef MXIT_DEBUG_EMO + purple_debug_info( MXIT_PLUGIN_ID, "emoticon_returned\n" ); +#endif + + if ( !url_text ) { + /* no reply from the WAP site */ + purple_debug_error( MXIT_PLUGIN_ID, "Error contacting the MXit WAP site. Please try again later (emoticon).\n" ); + goto done; + } + +#ifdef MXIT_DEBUG_EMO + hex_dump( data, len ); +#endif + + /* parse out the emoticon */ + pos = 0; + + /* validate the binary data received from the wapsite */ + if ( memcmp( MXIT_FRAME_MAGIC, &data[pos], strlen( MXIT_FRAME_MAGIC ) ) != 0 ) { + /* bad data, magic constant is wrong */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad magic)\n" ); + goto done; + } + pos += strlen( MXIT_FRAME_MAGIC ); + + /* validate the image frame desc byte */ + if ( data[pos] != '\x6F' ) { + /* bad frame desc */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad frame desc)\n" ); + goto done; + } + pos++; + + /* get the data length */ + res = asn_getlength( &data[pos], &em_size ); + if ( res <= 0 ) { + /* bad frame length */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad frame length)\n" ); + goto done; + } + pos += res; +#ifdef MXIT_DEBUG_EMO + purple_debug_info( MXIT_PLUGIN_ID, "read the length '%i'\n", em_size ); +#endif + + /* utf-8 (emoticon name) */ + res = asn_getUtf8( &data[pos], 0x0C, &str ); + if ( res <= 0 ) { + /* bad utf-8 string */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad name string)\n" ); + goto done; + } + pos += res; +#ifdef MXIT_DEBUG_EMO + purple_debug_info( MXIT_PLUGIN_ID, "read the string '%s'\n", str ); +#endif + g_free( str ); + str = NULL; + + /* utf-8 (emoticon shortcut) */ + res = asn_getUtf8( &data[pos], 0x81, &str ); + if ( res <= 0 ) { + /* bad utf-8 string */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad shortcut string)\n" ); + goto done; + } + pos += res; +#ifdef MXIT_DEBUG_EMO + purple_debug_info( MXIT_PLUGIN_ID, "read the string '%s'\n", str ); +#endif + em_id = str; + + /* validate the image data type */ + if ( data[pos] != '\x82' ) { + /* bad frame desc */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad data type)\n" ); + g_free( em_id ); + goto done; + } + pos++; + + /* get the data length */ + res = asn_getlength( &data[pos], &em_size ); + if ( res <= 0 ) { + /* bad frame length */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad data length)\n" ); + g_free( em_id ); + goto done; + } + pos += res; +#ifdef MXIT_DEBUG_EMO + purple_debug_info( MXIT_PLUGIN_ID, "read the length '%i'\n", em_size ); +#endif + + if ( g_hash_table_lookup( mx->session->iimages, em_id ) ) { + /* emoticon found in the table, so ignore this one */ + goto done; + } + + /* make a copy of the data */ + em_data = g_malloc( em_size ); + memcpy( em_data, &data[pos], em_size ); + + /* strip the mxit markup tags from the emoticon id */ + if ( ( em_id[0] == '.' ) && ( em_id[1] == '{' ) ) { + parse_emoticon_str( &em_id[2], emo ); + strcpy( em_id, emo ); + } + + /* we now have the emoticon, store it in the imagestore */ + id = purple_imgstore_add_with_id( em_data, em_size, NULL ); + + /* map the mxit emoticon id to purple image id */ + intptr = g_malloc( sizeof( int ) ); + *intptr = id; + g_hash_table_insert( mx->session->iimages, em_id, intptr ); + + mx->flags |= PURPLE_MESSAGE_IMAGES; +done: + mx->img_count--; + if ( ( mx->img_count == 0 ) && ( mx->converted ) ) { + /* + * this was the last outstanding emoticon for this message, + * so we can now display it to the user. + */ + mxit_show_message( mx ); + } +} + + +/*------------------------------------------------------------------------ + * Send a request to the MXit WAP site to download the specified emoticon. + * + * @param mx The Markup message object + * @param id The ID for the emoticon + */ +static void emoticon_request( struct RXMsgData* mx, const char* id ) +{ + PurpleUtilFetchUrlData* url_data; + const char* wapserver; + char* url; + + purple_debug_info( MXIT_PLUGIN_ID, "sending request for emoticon '%s'\n", id ); + + wapserver = purple_account_get_string( mx->session->acc, MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE ); + + /* reference: "libpurple/util.h" */ + url = g_strdup_printf( "%s/res/?type=emo&mlh=%i&sc=%s&ts=%li", wapserver, MXIT_EMOTICON_SIZE, id, time( NULL ) ); + url_data = purple_util_fetch_url_request( url, TRUE, NULL, TRUE, NULL, FALSE, emoticon_returned, mx ); + g_free( url ); +} + + +/*------------------------------------------------------------------------ + * Parse a Vibe command. + * + * @param mx The Markup message object + * @param message The message text (which contains the vibe) + * @return id The length of the message to skip + */ +static int mxit_parse_vibe( struct RXMsgData* mx, const char* message ) +{ + int vibeid; + + vibeid = message[2] - '0'; + + purple_debug_info( MXIT_PLUGIN_ID, "Vibe received (%i)\n", vibeid ); + + if ( vibeid > ( ARRAY_SIZE( vibes ) - 1 ) ) { + purple_debug_warning( MXIT_PLUGIN_ID, "Unsupported vibe received (%i)\n", vibeid ); + /* unsupported vibe */ + return 0; + } + + g_string_append_printf( mx->msg, "<font color=\"%s\"><i>%s Vibe...</i></font>", MXIT_VIBE_MSG_COLOR, vibes[vibeid] ); + return 2; +} + + +/*------------------------------------------------------------------------ + * Extract the nickname from a chatroom message and display it nicely in + * libPurple-style (HTML) markup. + * + * @param mx The received message data object + * @param message The message text + * @return The length of the message to skip + */ +static int mxit_extract_chatroom_nick( struct RXMsgData* mx, char* message, int len ) +{ + int i; + + if ( message[0] == '<' ) { + /* + * The message MIGHT contains an embedded nickname. But we can't + * be sure unless we find the end-of-nickname sequence: (>\n) + * Search for it.... + */ + gboolean found = FALSE; + gchar* nickname; + + for ( i = 1; i < len; i++ ) { + if ( ( message[i] == '\n' ) && ( message[i-1] == '>' ) ) { + found = TRUE; + message[i-1] = '\0'; /* loose the '>' */ + i++; /* and skip the new-line */ + break; + } + } + + if ( found ) { + /* + * The message definitely had an embedded nickname - generate a marked-up + * message to be displayed. + */ + nickname = g_markup_escape_text( &message[1], -1 ); + + /* add nickname within some BOLD markup to the new converted message */ + g_string_append_printf( mx->msg, "<b>%s:</b> ", nickname ); + + /* free up the resources */ + g_free( nickname ); + + return i; + } + } + + return 0; +} + + + +/*------------------------------------------------------------------------ + * Convert a message containing MXit protocol markup to libPurple-style (HTML) markup. + * + * @param mx The received message data object + * @param message The message text + * @param len The length of the message + */ +void mxit_parse_markup( struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags ) +{ + char tmpstr1[128]; + char* ch; + int i = 0; + + /* tags */ + gboolean tag_bold = FALSE; + gboolean tag_under = FALSE; + gboolean tag_italic = FALSE; + +#ifdef MXIT_DEBUG_MARKUP + purple_debug_info( MXIT_PLUGIN_ID, "Markup RX (original): '%s'\n", message ); +#endif + + + /* + * supported MXit markup: + * '*' bold + * '_' underline + * '/' italics + * '$' highlight text + * '.+' inc font size + * '.-' dec font size + * '#XXXXXX' foreground color + * '.{XX}' custom emoticon + * '\' escape the following character + * '::' MXit commands + */ + + + if ( is_mxit_chatroom_contact( mx->session, mx->from ) ) { + /* chatroom message, so we need to extract and skip the sender's nickname + * which is embedded inside the message */ + i = mxit_extract_chatroom_nick( mx, message, len ); + } + + /* run through the message and check for custom emoticons and markup */ + for ( ; i < len; i++ ) { + switch ( message[i] ) { + + + /* mxit markup parsing */ + case '*' : + if ( !( msgflags & CP_MSG_MARKUP ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + + /* bold markup */ + if ( !tag_bold ) + g_string_append( mx->msg, "<b>" ); + else + g_string_append( mx->msg, "</b>" ); + tag_bold = !tag_bold; + break; + case '_' : + if ( !( msgflags & CP_MSG_MARKUP ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + + /* underscore markup */ + if ( !tag_under ) + g_string_append( mx->msg, "<u>" ); + else + g_string_append( mx->msg, "</u>" ); + tag_under = !tag_under; + break; + case '/' : + if ( !( msgflags & CP_MSG_MARKUP ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + + /* italics markup */ + if ( !tag_italic ) + g_string_append( mx->msg, "<i>" ); + else + g_string_append( mx->msg, "</i>" ); + tag_italic = !tag_italic; + break; + case '$' : + if ( !( msgflags & CP_MSG_MARKUP ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + else if ( i + 1 >= len ) { + /* message too short for complete link */ + g_string_append_c( mx->msg, '$' ); + break; + } + + /* find the end tag */ + ch = strstr( &message[i + 1], "$" ); + if ( ch ) { + /* end found */ + *ch = '\0'; + mxit_add_html_link( mx, &message[i + 1], &message[i + 1] ); + *ch = '$'; + i += ( ch - &message[i + 1] ) + 1; + } + else { + g_string_append_c( mx->msg, message[i] ); + } + /* highlight text */ + break; + case '#' : + if ( !( msgflags & CP_MSG_MARKUP ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + else if ( i + COLORCODE_LEN >= len ) { + /* message too short for complete colour code */ + g_string_append_c( mx->msg, '#' ); + break; + } + + /* foreground (text) color */ + memcpy( tmpstr1, &message[i + 1], COLORCODE_LEN ); + tmpstr1[ COLORCODE_LEN ] = '\0'; /* terminate string */ + if ( strcmp( tmpstr1, "??????" ) == 0 ) { + /* need to reset the font */ + g_string_append( mx->msg, "</font>" ); + i += COLORCODE_LEN; + } + else if ( strspn( tmpstr1, "0123456789abcdefABCDEF") == COLORCODE_LEN ) { + /* definitely a numeric colour code */ + g_string_append_printf( mx->msg, "<font color=\"#%s\">", tmpstr1 ); + i += COLORCODE_LEN; + } + else { + /* not valid colour markup */ + g_string_append_c( mx->msg, '#' ); + } + break; + case '.' : + if ( !( msgflags & CP_MSG_EMOTICON ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + else if ( i + 1 >= len ) { + /* message too short */ + g_string_append_c( mx->msg, '.' ); + break; + } + + switch ( message[i+1] ) { + case '+' : + /* increment text size */ + g_string_append( mx->msg, "<font size=\"+1\">" ); + i++; + break; + case '-' : + /* decrement text size */ + g_string_append( mx->msg, "<font size=\"-1\">" ); + i++; + break; + case '{' : + /* custom emoticon */ + if ( i + 2 >= len ) { + /* message too short */ + g_string_append_c( mx->msg, '.' ); + break; + } + + parse_emoticon_str( &message[i+2], tmpstr1 ); + if ( tmpstr1[0] != '\0' ) { + mx->got_img = TRUE; + + if ( g_hash_table_lookup( mx->session->iimages, tmpstr1 ) ) { + /* emoticon found in the cache, so we do not have to request it from the WAPsite */ + } + else { + /* request emoticon from the WAPsite */ + mx->img_count++; + emoticon_request( mx, tmpstr1 ); + } + + g_string_append_printf( mx->msg, MXIT_II_TAG"%s>", tmpstr1 ); + i += strlen( tmpstr1 ) + 2; + } + else + g_string_append_c( mx->msg, '.' ); + + break; + default : + g_string_append_c( mx->msg, '.' ); + break; + } + break; + case '\\' : + if ( i + 1 >= len ) { + /* message too short for an escaped character */ + g_string_append_c( mx->msg, '\\' ); + } + else { + /* ignore the next character, because its been escaped */ + g_string_append_c( mx->msg, message[i + 1] ); + i++; + } + break; + + + /* command parsing */ + case ':' : + if ( i + 1 >= len ) { + /* message too short */ + g_string_append_c( mx->msg, ':' ); + break; + } + + if ( message[i+1] == '@' ) { + /* this is a vibe! */ + int size; + + if ( i + 2 >= len ) { + /* message too short */ + g_string_append_c( mx->msg, message[i] ); + break; + } + + size = mxit_parse_vibe( mx, &message[i] ); + if ( size == 0 ) + g_string_append_c( mx->msg, message[i] ); + else + i += size; + } + else if ( msgtype != CP_MSGTYPE_COMMAND ) { + /* this is not a command message */ + g_string_append_c( mx->msg, message[i] ); + } + else if ( message[i+1] == ':' ) { + /* parse out the command */ + int size; + + size = mxit_parse_command( mx, &message[i] ); + if ( size == 0 ) + g_string_append_c( mx->msg, ':' ); + else + i += size; + } + else { + g_string_append_c( mx->msg, ':' ); + } + break; + + + /* these aren't MXit markup, but are interpreted by libPurple */ + case '<' : + g_string_append( mx->msg, "<" ); + break; + case '>' : + g_string_append( mx->msg, ">" ); + break; + case '&' : + g_string_append( mx->msg, "&" ); + break; + case '"' : + g_string_append( mx->msg, """ ); + break; + + default : + /* text */ + g_string_append_c( mx->msg, message[i] ); + break; + } + } +} + + +/*------------------------------------------------------------------------ + * Insert an inline image command. + * + * @param mx The message text as processed so far. + * @oaram id The imgstore ID of the inline image. + */ +static void inline_image_add( GString* mx, int id ) +{ + PurpleStoredImage *image; + gconstpointer img_data; + gsize img_size; + gchar* enc; + + image = purple_imgstore_find_by_id( id ); + if ( image == NULL ) + return; + + img_data = purple_imgstore_get_data( image ); + img_size = purple_imgstore_get_size( image ); + + enc = purple_base64_encode( img_data, img_size ); + + g_string_append( mx, "::op=img|dat=" ); + g_string_append( mx, enc ); + g_string_append_c( mx, ':' ); + + g_free( enc ); +} + + +/*------------------------------------------------------------------------ + * Convert libpurple (HTML) markup to MXit protocol markup (for sending to MXit). + * Any MXit markup codes in the original message also need to be escaped. + * + * @param message The message text containing libPurple (HTML) markup + * @return The message text containing MXit markup + */ +char* mxit_convert_markup_tx( const char* message, int* msgtype ) +{ + GString* mx; + struct tag* tag; + GList* entry; + GList* tagstack = NULL; + char* reply; + char color[8]; + int len = strlen ( message ); + int i; + +#ifdef MXIT_DEBUG_MARKUP + purple_debug_info( MXIT_PLUGIN_ID, "Markup TX (original): '%s'\n", message ); +#endif + + /* + * libPurple uses the following HTML markup codes: + * Bold: <b>...</b> + * Italics: <i>...</i> + * Underline: <u>...</u> + * Strikethrough: <s>...</s> (NO MXIT SUPPORT) + * Font size: <font size="">...</font> + * Font type: <font face="">...</font> (NO MXIT SUPPORT) + * Font colour: <font color=#">...</font> + * Links: <a href="">...</a> + * Newline: <br> + * Inline image: <IMG ID=""> + * The following characters are also encoded: + * & " < > + */ + + /* new message data */ + mx = g_string_sized_new( len ); + + /* run through the message and check for HTML markup */ + for ( i = 0; i < len; i++ ) { + + switch ( message[i] ) { + case '<' : + if ( purple_str_has_prefix( &message[i], "<b>" ) || purple_str_has_prefix( &message[i], "</b>" ) ) { + /* bold */ + g_string_append_c( mx, '*' ); + } + else if ( purple_str_has_prefix( &message[i], "<i>" ) || purple_str_has_prefix( &message[i], "</i>" ) ) { + /* italics */ + g_string_append_c( mx, '/' ); + } + else if ( purple_str_has_prefix( &message[i], "<u>" ) || purple_str_has_prefix( &message[i], "</u>" ) ) { + /* underline */ + g_string_append_c( mx, '_' ); + } + else if ( purple_str_has_prefix( &message[i], "<br>" ) ) { + /* newline */ + g_string_append_c( mx, '\n' ); + } + else if ( purple_str_has_prefix( &message[i], "<font size=" ) ) { + /* font size */ + tag = g_new0( struct tag, 1 ); + tag->type = MXIT_TAG_SIZE; + tagstack = g_list_prepend( tagstack, tag ); + // TODO: implement size control + } + else if ( purple_str_has_prefix( &message[i], "<font color=" ) ) { + /* font colour */ + tag = g_new0( struct tag, 1 ); + tag->type = MXIT_TAG_COLOR; + tagstack = g_list_append( tagstack, tag ); + memset( color, 0x00, sizeof( color ) ); + memcpy( color, &message[i + 13], 7 ); + g_string_append( mx, color ); + } + else if ( purple_str_has_prefix( &message[i], "</font>" ) ) { + /* end of font tag */ + entry = g_list_last( tagstack ); + if ( entry ) { + tag = entry->data; + if ( tag->type == MXIT_TAG_COLOR ) { + /* font color reset */ + g_string_append( mx, "#??????" ); + } + else if ( tag->type == MXIT_TAG_SIZE ) { + /* font size */ + // TODO: implement size control + } + tagstack = g_list_remove( tagstack, tag ); + g_free( tag ); + } + } + else if ( purple_str_has_prefix( &message[i], "<IMG ID=" ) ) { + /* inline image */ + int imgid; + + if ( sscanf( &message[i+9], "%i", &imgid ) ) { + inline_image_add( mx, imgid ); + *msgtype = CP_MSGTYPE_COMMAND; /* inline image must be sent as a MXit command */ + } + } + + /* skip to end of tag ('>') */ + for ( i++; ( i < len ) && ( message[i] != '>' ) ; i++ ); + + break; + + case '*' : /* MXit bold */ + case '_' : /* MXit underline */ + case '/' : /* MXit italic */ + case '#' : /* MXit font color */ + case '$' : /* MXit highlight text */ + case '\\' : /* MXit escape backslash */ + g_string_append( mx, "\\" ); /* escape character */ + g_string_append_c( mx, message[i] ); /* character to escape */ + break; + + default: + g_string_append_c( mx, message[i] ); + break; + } + } + + /* unescape HTML entities to their literal characters (reference: "libpurple/utils.h") */ + reply = purple_unescape_html( mx->str ); + + g_string_free( mx, TRUE ); + +#ifdef MXIT_DEBUG_MARKUP + purple_debug_info( MXIT_PLUGIN_ID, "Markup TX (converted): '%s'\n", reply ); +#endif + + return reply; +} + + +/*------------------------------------------------------------------------ + * Free an emoticon entry. + * + * @param key MXit emoticon ID + * @param value Imagestore ID for emoticon + * @param user_data NULL (unused) + * @return TRUE + */ +static gboolean emoticon_entry_free( gpointer key, gpointer value, gpointer user_data ) +{ + int* imgid = value; + + /* key is a string */ + g_free( key ); + + /* value is 'id' in imagestore */ + purple_imgstore_unref_by_id( *imgid ); + g_free( value ); + + return TRUE; +} + + +/*------------------------------------------------------------------------ + * Free all entries in the emoticon cache. + * + * @param session The MXit session object + */ +void mxit_free_emoticon_cache( struct MXitSession* session ) +{ + g_hash_table_foreach_remove( session->iimages, emoticon_entry_free, NULL ); + g_hash_table_destroy ( session->iimages ); +}