pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
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
,
"<"
);
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
;
}
}
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:
* & " < >
*/
/* 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
);
}