Mercurial > pidgin
view libpurple/protocols/mxit/markup.c @ 30725:de59c181e6aa
MsnUserEndpoint->id isn't actually used, so let's not store it in the
struct. It's used as the key in the endpoints hashtable, so hopefully
we can get away with keeping only one copy of it
author | Mark Doliner <mark@kingant.net> |
---|---|
date | Thu, 22 Apr 2010 20:51:24 +0000 |
parents | 259bbfb423d4 |
children | d7325448badb |
line wrap: on
line source
/* * 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 "internal.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 */ N_( "Cool Vibrations" ), /* 1 */ N_( "Purple Rain" ), /* 2 */ N_( "Polite" ), /* 3 */ N_( "Rock n Roll" ), /* 4 */ N_( "Summer Slumber" ), /* 5 */ N_( "Electric Razor" ), /* 6 */ N_( "S.O.S" ), /* 7 */ N_( "Jack Hammer" ), /* 8 */ N_( "Bumble Bee" ), /* 9 */ N_( "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 */ 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 ); }