pidgin/pidgin

Use gchar for xdg dirs variables
xdg-dirs
2017-06-30, qarkai
d2408369eb7c
Use gchar for xdg dirs variables
/*
* 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 "debug.h"
#include "http.h"
#include "image-store.h"
#include "client.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"
#define MXIT_FAREWELL_MSG_COLOR "#949494"
/* 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 gchar* 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 ", buf[i] );
if ( i % 16 == 15 ) {
pos += sprintf( &msg[pos], "\n" );
purple_debug_info( MXIT_PLUGIN_ID, "%s", 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, "%s", msg );
pos = 0;
}
}
#endif
/*------------------------------------------------------------------------
* Adds a link to a message
*
* @param mx The Markup message object
* @param replydata This is the what will be returned when the link gets clicked
* @param isStructured Indicates that the reply is a structured reply
* @param displaytext This is the text for the link which will be displayed in the UI
*/
void mxit_add_html_link( struct RXMsgData* mx, const char* replydata, gboolean isStructured, const char* displaytext )
{
#ifdef MXIT_LINK_CLICK
gchar* link = NULL;
gchar* link64 = NULL;
/*
* The link content is encoded as follows:
* MXIT_LINK_KEY | ACCOUNT_USER | ACCOUNT_PROTO | REPLY_TO | REPLY_FORMAT | REPLY_DATA
*/
link = g_strdup_printf( "%s|%s|%s|%s|%i|%s",
MXIT_LINK_KEY,
purple_account_get_username( mx->session->acc ),
purple_account_get_protocol_id( mx->session->acc ),
mx->from,
isStructured ? 1 : 0,
replydata );
link64 = purple_base64_encode( (const unsigned char*) link, strlen( link ) );
g_string_append_printf( mx->msg, "<a href=\"%s%s\">%s</a>", MXIT_LINK_PREFIX, link64, displaytext );
g_free( link64 );
g_free( link );
#else
g_string_append_printf( mx->msg, "<b>%s</b>", replydata );
#endif
}
/*------------------------------------------------------------------------
* Extract an ASN.1 formatted length field from the data.
*
* @param data The source data
* @param data_len Length of data
* @param size The extracted length
* @return The number of bytes extracted
*/
static unsigned int asn_getlength( const gchar* data, gsize data_len, int* size )
{
unsigned int len = 0;
unsigned char bytes;
unsigned char byte;
int i;
if ( data_len < 1 ) {
/* missing first byte! */
return -1;
}
/* 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++;
if ( data_len - 1 < bytes ) {
/* missing length! */
return -1;
}
/* 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 data_len Length of 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 gchar* data, gsize data_len, gchar type, char** utf8 )
{
unsigned int len;
gchar *out_str;
if ( data_len < 2 ) {
/* missing type or length! */
return -1;
}
/* 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 (got 0x%02X, expected 0x%02X)\n", data[0], type );
return -1;
}
len = (guint8)data[1]; /* length field [1 byte] */
if ( data_len - 2 < len ) {
/* not enough bytes left in data! */
return -1;
}
out_str = g_malloc(len + 1);
memcpy(out_str, &data[2], len); /* data field */
out_str[len] = '\0';
*utf8 = out_str;
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 );
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 )
{
GString* msg = NULL;
char* ch = NULL;
unsigned int pos = 0;
unsigned int start = 0;
unsigned int l_nl = 0;
unsigned int l_sp = 0;
unsigned int l_gt = 0;
unsigned int stop = 0;
int tags = 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 separately 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--;
}
/* push message to pidgin */
purple_serv_got_im( mx->session->con, mx->from, msg->str, mx->flags, mx->timestamp );
g_string_free( msg, TRUE );
msg = NULL;
/* next part need this flag set */
mx->flags |= PURPLE_MESSAGE_RAW;
tags = 0;
start = stop + 1;
pos = start;
}
else
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';
/* push message to pidgin */
purple_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;
char tag[64];
if ( mx->got_img ) {
/* search and replace all emoticon tags with proper image tags */
while ( ( pos = strstr( mx->msg->str, MXIT_II_TAG ) ) != NULL ) {
PurpleImage *img;
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;
ii = g_strndup( &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 = g_hash_table_lookup(mx->session->inline_images, ii);
if (img == NULL) {
/* inline image not found, so we will just skip it */
purple_debug_error( MXIT_PLUGIN_ID, "inline image NOT found (%s)\n", ii );
} else {
guint img_id;
img_id = purple_image_store_add_temporary(img);
/* insert img tag */
g_snprintf(tag, sizeof(tag), "<img src=\""
PURPLE_IMAGE_STORE_PROTOCOL "%u\">",
img_id);
g_string_insert( mx->msg, start, tag );
}
g_free( ii );
}
}
#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 */
purple_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.
*/
static void emoticon_returned(PurpleHttpConnection *http_conn,
PurpleHttpResponse *response, gpointer user_data)
{
PurpleImage *img;
struct RXMsgData* mx = (struct RXMsgData*) user_data;
const gchar* data;
size_t len;
unsigned int pos = 0;
char* str;
int em_size = 0;
char* em_data = NULL;
char* em_id = NULL;
int res;
purple_debug_info( MXIT_PLUGIN_ID, "emoticon_returned\n" );
if (!purple_http_response_is_successful(response)) {
/* 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;
}
data = purple_http_response_get_data(response, &len);
#ifdef MXIT_DEBUG_EMO
hex_dump( data, len );
#endif
/* validate that the returned data starts with the magic constant that indicates it is a custom emoticon */
if ( len - pos < strlen( MXIT_FRAME_MAGIC ) || memcmp( MXIT_FRAME_MAGIC, &data[pos], strlen( MXIT_FRAME_MAGIC ) ) != 0 ) {
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' ) {
purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad frame desc)\n" );
goto done;
}
pos++;
/* get the frame image data length */
res = asn_getlength( &data[pos], len - pos, &em_size );
if ( res <= 0 ) {
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], len - pos, 0x0C, &str );
if ( res <= 0 ) {
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], len - pos, 0x81, &str );
if ( res <= 0 ) {
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 ( len - pos < 1 || data[pos] != '\x82' ) {
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], len - 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 ( len - pos < (gsize)em_size ) {
/* not enough bytes left in data! */
purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (data length too long)\n");
g_free( em_id );
goto done;
}
/* strip the mxit markup tags from the emoticon id (eg, .{XY} -> XY) */
if ( ( em_id[0] == '.' ) && ( em_id[1] == '{' ) ) {
char emo[MXIT_MAX_EMO_ID + 1];
parse_emoticon_str( &em_id[2], emo );
strcpy( em_id, emo );
}
if (g_hash_table_lookup(mx->session->inline_images, em_id)) {
/* emoticon found in the table, so ignore this one */
g_free( em_id );
goto done;
}
/* make a copy of the data */
em_data = g_malloc( em_size );
memcpy( em_data, &data[pos], em_size );
/* map the mxit emoticon id to purple image */
img = purple_image_new_from_data(em_data, em_size);
g_hash_table_insert(mx->session->inline_images, em_id, img);
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 )
{
const char* wapserver;
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 );
purple_http_connection_set_add(mx->session->async_http_reqs,
purple_http_get_printf(mx->session->con, emoticon_returned, mx,
"%s/res/?type=emo&mlh=%i&sc=%s&ts=%li", wapserver,
MXIT_EMOTICON_SIZE, id, time( NULL ) ));
}
/*------------------------------------------------------------------------
* 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 )
{
unsigned 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 msgflags )
{
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;
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 ) {
gchar* nickname;
/*
* The message definitely had an embedded nickname - generate a marked-up
* message to be displayed.
*/
nickname = g_markup_escape_text( &message[1], -1 );
/* Remove any MXit escaping from nickname ("\X" --> "X") */
if ( msgflags & CP_MSG_MARKUP ) {
int nicklen = strlen( nickname );
int j, k;
for ( j = 0, k = 0; j < nicklen; j++ ) {
if ( nickname[j] == '\\' )
j++;
nickname[k] = nickname[j];
k++;
}
nickname[k] = '\0'; /* terminate string */
}
/* 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;
int font_size = 0;
#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, msgflags );
}
/* 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], FALSE, &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 ( i + 1 >= len ) {
/* message too short */
g_string_append_c( mx->msg, '.' );
break;
}
if ( ( msgflags & CP_MSG_EMOTICON ) && ( message[i+1] == '{' ) ) {
/* 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->inline_images, 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, '.' );
}
else if ( ( msgflags & CP_MSG_MARKUP ) && ( message[i+1] == '+' ) ) {
/* increment text size */
font_size++;
g_string_append_printf( mx->msg, "<font size=\"%+i\">", font_size );
i++;
}
else if ( ( msgflags & CP_MSG_MARKUP ) && ( message[i+1] == '-' ) ) {
/* decrement text size */
font_size--;
g_string_append_printf( mx->msg, "<font size=\"%+i\">", font_size );
i++;
}
else
g_string_append_c( mx->msg, '.' );
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, "&lt;" );
break;
case '>' :
g_string_append( mx->msg, "&gt;" );
break;
case '&' :
g_string_append( mx->msg, "&amp;" );
break;
case '"' :
g_string_append( mx->msg, "&quot;" );
break;
default :
/* text */
g_string_append_c( mx->msg, message[i] );
break;
}
}
if ( msgflags & CP_MSG_FAREWELL ) {
/* this is a farewell message */
g_string_prepend( mx->msg, "<font color=\""MXIT_FAREWELL_MSG_COLOR"\"><i>" );
g_string_append( mx->msg, "</i></font>" );
}
}
/*------------------------------------------------------------------------
* Insert an inline image command.
*
* @param mx The message text as processed so far.
* @oaram image The PurpleImage of the inline image.
*/
static void
inline_image_add(GString* mx, PurpleImage *image)
{
gconstpointer img_data;
gsize img_size;
gchar* enc;
img_data = purple_image_get_data(image);
img_size = purple_image_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;
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 SRC="">
* The following characters are also encoded:
* &amp; &quot; &lt; &gt;
*/
/* 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 */
int fontsize;
tag = g_new0( struct tag, 1 );
tag->type = MXIT_TAG_SIZE;
tagstack = g_list_prepend( tagstack, tag );
// TODO: implement size control
if ( sscanf( &message[i+12], "%i", &fontsize ) ) {
purple_debug_info( MXIT_PLUGIN_ID, "Font size set to %i\n", fontsize );
}
}
else if ( purple_str_has_prefix( &message[i], "<font color=" ) ) {
/* font colour */
char color[8];
/* ensure we have the complete tag: <font color="#123456"> */
if ( i + 20 < len ) {
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 src=\"")) {
/* inline image */
PurpleImage *img;
img = purple_image_store_get_from_uri(
&message[i + sizeof("<img src=\"") - 1]);
if (img) {
inline_image_add(mx, img);
*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;
case '.' : /* might be a MXit font size change, or custom emoticon */
if ( i + 1 < len ) {
if ( ( message[i+1] == '+' ) || ( message[i+1] == '-' ) )
g_string_append( mx, "\\." ); /* escape "." */
else
g_string_append_c( mx, '.' );
}
else
g_string_append_c( mx, '.' );
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 all entries in the emoticon cache.
*
* @param session The MXit session object
*/
void mxit_free_emoticon_cache( struct MXitSession* session )
{
g_hash_table_destroy(session->inline_images);
}