summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Ruprecht <maiku@pidgin.im>2009-11-11 02:06:19 +0000
committerMichael Ruprecht <maiku@pidgin.im>2009-11-11 02:06:19 +0000
commit238c757020374e509c7c0d78c9d170720d62e31f (patch)
tree37592ff4085b9c87bfec43bafda3f25af830c527
parent43d7b04d9c00b4e89ac576662a0a408fe8de31e5 (diff)
parent8b40cb0d575b1b410452273cd13b027acd4f747b (diff)
downloadpidgin-238c757020374e509c7c0d78c9d170720d62e31f.tar.gz
merge of 'c18c0ba16b64ccc78a132834164b57460f7263bd'
and '154a555ace57ad3e2d00e03df3b0a32391e13ab4'
-rw-r--r--COPYRIGHT3
-rw-r--r--ChangeLog2
-rw-r--r--configure.ac8
-rw-r--r--libpurple/protocols/Makefile.am2
-rw-r--r--libpurple/protocols/jabber/jabber.h11
-rw-r--r--libpurple/protocols/mxit/Makefile.am63
-rw-r--r--libpurple/protocols/mxit/Makefile.mingw91
-rw-r--r--libpurple/protocols/mxit/actions.c437
-rw-r--r--libpurple/protocols/mxit/actions.h34
-rw-r--r--libpurple/protocols/mxit/aes.c405
-rw-r--r--libpurple/protocols/mxit/aes.h39
-rw-r--r--libpurple/protocols/mxit/chunk.c659
-rw-r--r--libpurple/protocols/mxit/chunk.h140
-rw-r--r--libpurple/protocols/mxit/cipher.c111
-rw-r--r--libpurple/protocols/mxit/cipher.h36
-rw-r--r--libpurple/protocols/mxit/filexfer.c454
-rw-r--r--libpurple/protocols/mxit/filexfer.h50
-rw-r--r--libpurple/protocols/mxit/formcmds.c397
-rw-r--r--libpurple/protocols/mxit/formcmds.h35
-rw-r--r--libpurple/protocols/mxit/http.c331
-rw-r--r--libpurple/protocols/mxit/http.h47
-rw-r--r--libpurple/protocols/mxit/login.c789
-rw-r--r--libpurple/protocols/mxit/login.h45
-rw-r--r--libpurple/protocols/mxit/markup.c1192
-rw-r--r--libpurple/protocols/mxit/markup.h40
-rw-r--r--libpurple/protocols/mxit/multimx.c597
-rw-r--r--libpurple/protocols/mxit/multimx.h104
-rw-r--r--libpurple/protocols/mxit/mxit.c694
-rw-r--r--libpurple/protocols/mxit/mxit.h197
-rw-r--r--libpurple/protocols/mxit/profile.c160
-rw-r--r--libpurple/protocols/mxit/profile.h56
-rw-r--r--libpurple/protocols/mxit/protocol.c2442
-rw-r--r--libpurple/protocols/mxit/protocol.h304
-rw-r--r--libpurple/protocols/mxit/roster.c722
-rw-r--r--libpurple/protocols/mxit/roster.h139
-rw-r--r--libpurple/protocols/mxit/splashscreen.c223
-rw-r--r--libpurple/protocols/mxit/splashscreen.h59
-rw-r--r--pidgin/gtkmain.c2
-rw-r--r--pidgin/gtkprefs.c2
-rw-r--r--pidgin/pixmaps/Makefile.am3
-rw-r--r--pidgin/pixmaps/protocols/16/mxit.pngbin0 -> 490 bytes
-rw-r--r--pidgin/pixmaps/protocols/22/mxit.pngbin0 -> 725 bytes
-rw-r--r--pidgin/pixmaps/protocols/48/mxit.pngbin0 -> 1689 bytes
-rw-r--r--pidgin/pixmaps/protocols/scalable/mxit.svg24
-rw-r--r--pidgin/plugins/disco/gtkdisco.c8
-rw-r--r--pidgin/plugins/disco/gtkdisco.h2
-rw-r--r--po/POTFILES.in9
47 files changed, 11152 insertions, 16 deletions
diff --git a/COPYRIGHT b/COPYRIGHT
index 895d407cf2..a64ede71aa 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -276,6 +276,7 @@ Syd Logan
Lokheed
Norberto Lopes
Shlomi Loubaton
+Pieter Loubser
Brian Lu
Uli Luckas
Matthew Luckie
@@ -319,6 +320,7 @@ Tim Mooney
Sergio Moretto
Andrei Mozzhuhin
Christian Muise
+MXit Lifestyle (Pty) Ltd.
Richard Nelson
Dennis Nezic
Matthew A. Nicholson
@@ -497,6 +499,7 @@ Kristof Vansant
James Vega
David Vermeille
Sid Vicious
+Andrew Victor
Jorge VillaseƱor (Masca)
Bjoern Voigt
Wan Hing Wah
diff --git a/ChangeLog b/ChangeLog
index e0c64e0659..d016626518 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -4,6 +4,8 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
version 2.6.4 (??/??/20??):
libpurple:
* Actually emit the hold signal for media calls.
+ * Added "MXit" protocol plugin, supported and maintained by the MXit folks
+ themselves (MXit Lifestyle (Pty) Ltd.)
General:
* New 'plugins' sub-command to 'debug' command (i.e. '/debug plugins')
diff --git a/configure.ac b/configure.ac
index 3789bd5244..79f479c4d0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1078,7 +1078,7 @@ if test "x$STATIC_PRPLS" != "x" -a "x$DYNAMIC_PRPLS" = "xall"; then
fi
if test "x$STATIC_PRPLS" = "xall" ; then
- STATIC_PRPLS="bonjour gg irc jabber msn myspace novell oscar qq sametime silc simple yahoo zephyr"
+ STATIC_PRPLS="bonjour gg irc jabber msn myspace mxit novell oscar qq sametime silc simple yahoo zephyr"
fi
if test "x$have_meanwhile" != "xyes" ; then
STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/sametime//'`
@@ -1135,6 +1135,7 @@ for i in $STATIC_PRPLS ; do
msn) static_msn=yes ;;
msnp9) static_msn=yes ;;
myspace) static_myspace=yes ;;
+ mxit) static_mxit=yes ;;
novell) static_novell=yes ;;
oscar) static_oscar=yes ;;
aim) static_oscar=yes ;;
@@ -1155,6 +1156,7 @@ AM_CONDITIONAL(STATIC_IRC, test "x$static_irc" = "xyes")
AM_CONDITIONAL(STATIC_JABBER, test "x$static_jabber" = "xyes")
AM_CONDITIONAL(STATIC_MSN, test "x$static_msn" = "xyes")
AM_CONDITIONAL(STATIC_MYSPACE, test "x$static_myspace" = "xyes")
+AM_CONDITIONAL(STATIC_MXIT, test "x$static_mxit" = "xyes")
AM_CONDITIONAL(STATIC_NOVELL, test "x$static_novell" = "xyes")
AM_CONDITIONAL(STATIC_OSCAR, test "x$static_oscar" = "xyes")
AM_CONDITIONAL(STATIC_QQ, test "x$static_qq" = "xyes")
@@ -1169,7 +1171,7 @@ AC_DEFINE_UNQUOTED(STATIC_PROTO_INIT, $extern_init static void static_proto_init
AC_ARG_WITH(dynamic_prpls, [AC_HELP_STRING([--with-dynamic-prpls], [specify which protocols to build dynamically])], [DYNAMIC_PRPLS=`echo $withval | $sedpath 's/,/ /g'`])
if test "x$DYNAMIC_PRPLS" = "xall" ; then
- DYNAMIC_PRPLS="bonjour gg irc jabber msn myspace novell oscar qq sametime silc simple yahoo zephyr"
+ DYNAMIC_PRPLS="bonjour gg irc jabber msn myspace mxit novell oscar qq sametime silc simple yahoo zephyr"
fi
if test "x$have_meanwhile" != "xyes"; then
DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/sametime//'`
@@ -1196,6 +1198,7 @@ for i in $DYNAMIC_PRPLS ; do
msn) dynamic_msn=yes ;;
msnp9) dynamic_msn=yes ;;
myspace) dynamic_myspace=yes ;;
+ mxit) dynamic_mxit=yes ;;
novell) dynamic_novell=yes ;;
null) dynamic_null=yes ;;
oscar) dynamic_oscar=yes ;;
@@ -2529,6 +2532,7 @@ AC_OUTPUT([Makefile
libpurple/protocols/msn/Makefile
libpurple/protocols/msnp9/Makefile
libpurple/protocols/myspace/Makefile
+ libpurple/protocols/mxit/Makefile
libpurple/protocols/novell/Makefile
libpurple/protocols/null/Makefile
libpurple/protocols/oscar/Makefile
diff --git a/libpurple/protocols/Makefile.am b/libpurple/protocols/Makefile.am
index 97d1de9357..19a86c8974 100644
--- a/libpurple/protocols/Makefile.am
+++ b/libpurple/protocols/Makefile.am
@@ -1,5 +1,5 @@
EXTRA_DIST = Makefile.mingw
-DIST_SUBDIRS = bonjour gg irc jabber msn msnp9 myspace novell null oscar qq sametime silc silc10 simple yahoo zephyr
+DIST_SUBDIRS = bonjour gg irc jabber msn msnp9 myspace mxit novell null oscar qq sametime silc silc10 simple yahoo zephyr
SUBDIRS = $(DYNAMIC_PRPLS) $(STATIC_PRPLS)
diff --git a/libpurple/protocols/jabber/jabber.h b/libpurple/protocols/jabber/jabber.h
index 7440e674e4..d25fa67fd9 100644
--- a/libpurple/protocols/jabber/jabber.h
+++ b/libpurple/protocols/jabber/jabber.h
@@ -193,25 +193,16 @@ struct _JabberStream
char *serverFQDN;
- /* OK, this stays at the end of the struct, so plugins can depend
- * on the rest of the stuff being in the right place
- */
#ifdef HAVE_CYRUS_SASL
sasl_conn_t *sasl;
sasl_callback_t *sasl_cb;
-#else /* keep the struct the same size */
- void *sasl;
- void *sasl_cb;
-#endif
- /* did someone say something about the end of the struct? */
-#ifdef HAVE_CYRUS_SASL
const char *current_mech;
int auth_fail_count;
-#endif
int sasl_state;
int sasl_maxbuf;
GString *sasl_mechs;
+#endif
gboolean unregistration;
PurpleAccountUnregistrationCb unregistration_cb;
diff --git a/libpurple/protocols/mxit/Makefile.am b/libpurple/protocols/mxit/Makefile.am
new file mode 100644
index 0000000000..9d89cc946e
--- /dev/null
+++ b/libpurple/protocols/mxit/Makefile.am
@@ -0,0 +1,63 @@
+EXTRA_DIST = \
+ Makefile.mingw
+
+pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
+
+MXITSOURCES = \
+ actions.c \
+ actions.h \
+ aes.c \
+ aes.h \
+ chunk.c \
+ chunk.h \
+ cipher.c \
+ cipher.h \
+ filexfer.c \
+ filexfer.h \
+ formcmds.c \
+ formcmds.h \
+ http.c \
+ http.h \
+ login.c \
+ login.h \
+ markup.c \
+ markup.h \
+ multimx.c \
+ multimx.h \
+ mxit.c \
+ mxit.h \
+ profile.c \
+ profile.h \
+ protocol.c \
+ protocol.h \
+ roster.c \
+ roster.h \
+ splashscreen.c \
+ splashscreen.h
+
+
+AM_CFLAGS = $(st)
+
+libmxit_la_LDFLAGS = -module -avoid-version
+
+if STATIC_MXIT
+
+st = -DPURPLE_STATIC_PRPL
+noinst_LTLIBRARIES = libmxit.la
+libmxit_la_SOURCES = $(MXITSOURCES)
+libmxit_la_CFLAGS = $(AM_CFLAGS)
+
+else
+
+st =
+pkg_LTLIBRARIES = libmxit.la
+libmxit_la_SOURCES = $(MXITSOURCES)
+libmxit_la_LIBADD = $(GLIB_LIBS)
+
+endif
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/libpurple \
+ -I$(top_builddir)/libpurple \
+ $(GLIB_CFLAGS) \
+ $(DEBUG_CFLAGS)
diff --git a/libpurple/protocols/mxit/Makefile.mingw b/libpurple/protocols/mxit/Makefile.mingw
new file mode 100644
index 0000000000..00d0b6db61
--- /dev/null
+++ b/libpurple/protocols/mxit/Makefile.mingw
@@ -0,0 +1,91 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for win32 (mingw) version of libmxit
+#
+
+PIDGIN_TREE_TOP := ../../..
+include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+
+TARGET = libmxit
+TYPE = PLUGIN
+
+# Static or Plugin...
+ifeq ($(TYPE),STATIC)
+ DEFINES += -DSTATIC
+ DLL_INSTALL_DIR = $(PURPLE_INSTALL_DIR)
+else
+ifeq ($(TYPE),PLUGIN)
+ DLL_INSTALL_DIR = $(PURPLE_INSTALL_PLUGINS_DIR)
+endif
+endif
+
+##
+## INCLUDE PATHS
+##
+INCLUDE_PATHS += -I. \
+ -I$(GTK_TOP)/include \
+ -I$(GTK_TOP)/include/glib-2.0 \
+ -I$(GTK_TOP)/lib/glib-2.0/include \
+ -I$(PURPLE_TOP) \
+ -I$(PURPLE_TOP)/win32 \
+ -I$(PIDGIN_TREE_TOP)
+
+LIB_PATHS += -L$(GTK_TOP)/lib \
+ -L$(PURPLE_TOP)
+
+##
+## SOURCES, OBJECTS
+##
+C_SRC = actions.c \
+ aes.c \
+ chunk.c \
+ cipher.c \
+ filexfer.c \
+ formcmds.c \
+ http.c \
+ login.c \
+ markup.c \
+ multimx.c \
+ mxit.c \
+ profile.c \
+ protocol.c \
+ roster.c \
+ splashscreen.c
+
+OBJECTS = $(C_SRC:%.c=%.o)
+
+##
+## LIBRARIES
+##
+LIBS = \
+ -lglib-2.0 \
+ -lintl \
+ -lws2_32 \
+ -lpurple
+
+include $(PIDGIN_COMMON_RULES)
+
+##
+## TARGET DEFINITIONS
+##
+.PHONY: all install clean
+
+all: $(TARGET).dll
+
+install: all $(DLL_INSTALL_DIR)
+ cp $(TARGET).dll $(DLL_INSTALL_DIR)
+
+$(OBJECTS): $(PURPLE_CONFIG_H)
+
+$(TARGET).dll: $(PURPLE_DLL).a $(OBJECTS)
+ $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll
+
+##
+## CLEAN RULES
+##
+clean:
+ rm -f $(OBJECTS)
+ rm -f $(TARGET).dll
+
+include $(PIDGIN_COMMON_TARGETS)
diff --git a/libpurple/protocols/mxit/actions.c b/libpurple/protocols/mxit/actions.c
new file mode 100644
index 0000000000..eb231b4632
--- /dev/null
+++ b/libpurple/protocols/mxit/actions.c
@@ -0,0 +1,437 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- handle MXit plugin actions --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "purple.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "roster.h"
+#include "actions.h"
+#include "splashscreen.h"
+#include "cipher.h"
+#include "profile.h"
+
+
+/* MXit Moods */
+static const char* moods[] = {
+ /* 0 */ "None",
+ /* 1 */ "Angry",
+ /* 2 */ "Excited",
+ /* 3 */ "Grumpy",
+ /* 4 */ "Happy",
+ /* 5 */ "In Love",
+ /* 6 */ "Invincible",
+ /* 7 */ "Sad",
+ /* 8 */ "Hot",
+ /* 9 */ "Sick",
+ /* 10 */ "Sleepy"
+};
+
+
+/*------------------------------------------------------------------------
+ * The user has selected to change their current mood.
+ *
+ * @param gc The connection object
+ * @param fields The fields from the request pop-up
+ */
+static void mxit_cb_set_mood( PurpleConnection* gc, PurpleRequestFields* fields )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ int mood = purple_request_fields_get_choice( fields, "mood" );
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_set_mood (%i)\n", mood );
+
+ if ( !PURPLE_CONNECTION_IS_VALID( gc ) ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Unable to set mood; account offline.\n" );
+ return;
+ }
+
+ /* Save the new mood in session */
+ session->mood = mood;
+
+ /* now send the update to MXit */
+ mxit_send_mood( session, mood );
+}
+
+
+/*------------------------------------------------------------------------
+ * Create and display the mood selection window to the user.
+ *
+ * @param action The action object
+ */
+static void mxit_cb_action_mood( PurplePluginAction* action )
+{
+ PurpleConnection* gc = (PurpleConnection*) action->context;
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+
+ PurpleRequestFields* fields = NULL;
+ PurpleRequestFieldGroup* group = NULL;
+ PurpleRequestField* field = NULL;
+ unsigned int i = 0;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_action_mood\n" );
+
+ fields = purple_request_fields_new();
+ group = purple_request_field_group_new( NULL );
+ purple_request_fields_add_group( fields, group );
+
+ /* show current mood */
+ field = purple_request_field_string_new( "current", _( "Current Mood" ), _( moods[session->mood] ), FALSE );
+ purple_request_field_string_set_editable( field, FALSE ); /* current mood field is not editable */
+ purple_request_field_group_add_field( group, field );
+
+ /* add all moods to list */
+ field = purple_request_field_choice_new( "mood", _( "New Mood" ), 0 );
+ for ( i = 0; i < ARRAY_SIZE( moods ); i++ ) {
+ purple_request_field_choice_add( field, _( moods[i] ) );
+ }
+ purple_request_field_set_required( field, TRUE );
+ purple_request_field_choice_set_default_value( field, session->mood );
+ purple_request_field_group_add_field( group, field );
+
+ /* (reference: "libpurple/request.h") */
+ purple_request_fields( gc, _( "Mood" ), _( "Change your Mood" ), _( "How do you feel right now?" ), fields, _( "Set" ),
+ G_CALLBACK( mxit_cb_set_mood ), _( "Cancel" ), NULL, purple_connection_get_account( gc ), NULL, NULL, gc );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has selected to change their profile.
+ *
+ * @param gc The connection object
+ * @param fields The fields from the request pop-up
+ */
+static void mxit_cb_set_profile( PurpleConnection* gc, PurpleRequestFields* fields )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ PurpleRequestField* field = NULL;
+ const char* pin = NULL;
+ const char* pin2 = NULL;
+ const char* name = NULL;
+ const char* bday = NULL;
+ const char* err = NULL;
+ int len;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_set_profile\n" );
+
+ if ( !PURPLE_CONNECTION_IS_VALID( gc ) ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Unable to update profile; account offline.\n" );
+ return;
+ }
+
+ /* validate pin */
+ pin = purple_request_fields_get_string( fields, "pin" );
+ if ( !pin ) {
+ err = "The PIN you entered is invalid.";
+ goto out;
+ }
+ len = strlen( pin );
+ if ( ( len < 4 ) || ( len > 10 ) ) {
+ err = "The PIN you entered has an invalid length [4-10].";
+ goto out;
+ }
+ for ( i = 0; i < len; i++ ) {
+ if ( !g_ascii_isdigit( pin[i] ) ) {
+ err = "The PIN is invalid. It should only consist of digits [0-9].";
+ goto out;
+ }
+ }
+ pin2 = purple_request_fields_get_string( fields, "pin2" );
+ if ( ( !pin2 ) || ( strcmp( pin, pin2 ) != 0 ) ) {
+ err = "The two PINs you entered does not match.";
+ goto out;
+ }
+
+ /* validate name */
+ name = purple_request_fields_get_string( fields, "name" );
+ if ( ( !name ) || ( strlen( name ) < 3 ) ) {
+ err = "The name you entered is invalid.";
+ goto out;
+ }
+
+ /* validate birthdate */
+ bday = purple_request_fields_get_string( fields, "bday" );
+ if ( ( !bday ) || ( strlen( bday ) < 10 ) || ( !validateDate( bday ) ) ) {
+ err = "The birthday you entered is invalid. The correct format is: 'YYYY-MM-DD'.";
+ goto out;
+ }
+
+out:
+ if ( !err ) {
+ struct MXitProfile* profile = session->profile;
+ GString* attributes = g_string_sized_new( 128 );
+ char attrib[512];
+ unsigned int acount = 0;
+
+ /* all good, so we can now update the profile */
+
+ /* update pin */
+ purple_account_set_password( session->acc, pin );
+ g_free( session->encpwd );
+ session->encpwd = mxit_encrypt_password( session );
+
+ /* update name */
+ g_strlcpy( profile->nickname, name, sizeof( profile->nickname ) );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_FULLNAME, CP_PROF_TYPE_UTF8, profile->nickname );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update hidden */
+ field = purple_request_fields_get_field( fields, "hidden" );
+ profile->hidden = purple_request_field_bool_get_value( field );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_HIDENUMBER, CP_PROF_TYPE_BOOL, ( profile->hidden ) ? "1" : "0" );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update birthday */
+ strcpy( profile->birthday, bday );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_BIRTHDATE, CP_PROF_TYPE_UTF8, profile->birthday );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update gender */
+ if ( purple_request_fields_get_choice( fields, "male" ) == 0 )
+ profile->male = FALSE;
+ else
+ profile->male = TRUE;
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_GENDER, CP_PROF_TYPE_BOOL, ( profile->male ) ? "1" : "0" );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update title */
+ name = purple_request_fields_get_string( fields, "title" );
+ if ( !name )
+ profile->title[0] = '\0';
+ else
+ strcpy( profile->title, name );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_TITLE, CP_PROF_TYPE_UTF8, profile->title );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update firstname */
+ name = purple_request_fields_get_string( fields, "firstname" );
+ if ( !name )
+ profile->firstname[0] = '\0';
+ else
+ strcpy( profile->firstname, name );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_FIRSTNAME, CP_PROF_TYPE_UTF8, profile->firstname );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update lastname */
+ name = purple_request_fields_get_string( fields, "lastname" );
+ if ( !name )
+ profile->lastname[0] = '\0';
+ else
+ strcpy( profile->lastname, name );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_LASTNAME, CP_PROF_TYPE_UTF8, profile->lastname );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update email address */
+ name = purple_request_fields_get_string( fields, "email" );
+ if ( !name )
+ profile->email[0] = '\0';
+ else
+ strcpy( profile->email, name );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_EMAIL, CP_PROF_TYPE_UTF8, profile->email );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* update mobile number */
+ name = purple_request_fields_get_string( fields, "mobilenumber" );
+ if ( !name )
+ profile->mobilenr[0] = '\0';
+ else
+ strcpy( profile->mobilenr, name );
+ g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_MOBILENR, CP_PROF_TYPE_UTF8, profile->mobilenr );
+ g_string_append( attributes, attrib );
+ acount++;
+
+ /* send the profile update to MXit */
+ mxit_send_extprofile_update( session, session->encpwd, acount, attributes->str );
+ g_string_free( attributes, TRUE );
+ }
+ else {
+ /* show error to user */
+ mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Profile Update Error" ), _( err ) );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Display and update the user's profile.
+ *
+ * @param action The action object
+ */
+static void mxit_cb_action_profile( PurplePluginAction* action )
+{
+ PurpleConnection* gc = (PurpleConnection*) action->context;
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ struct MXitProfile* profile = session->profile;
+
+ PurpleRequestFields* fields = NULL;
+ PurpleRequestFieldGroup* group = NULL;
+ PurpleRequestField* field = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_action_profile\n" );
+
+ /* ensure that we actually have the user's profile information */
+ if ( !profile ) {
+ /* no profile information yet, so we cannot update */
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Profile" ), _( "Your profile information is not yet retrieved. Please try again later." ) );
+ return;
+ }
+
+ fields = purple_request_fields_new();
+ group = purple_request_field_group_new( NULL );
+ purple_request_fields_add_group( fields, group );
+
+ /* pin */
+ field = purple_request_field_string_new( "pin", _( "PIN" ), session->acc->password, FALSE );
+ purple_request_field_string_set_masked( field, TRUE );
+ purple_request_field_group_add_field( group, field );
+ field = purple_request_field_string_new( "pin2", _( "Verify PIN" ), session->acc->password, FALSE );
+ purple_request_field_string_set_masked( field, TRUE );
+ purple_request_field_group_add_field( group, field );
+
+ /* display name */
+ field = purple_request_field_string_new( "name", _( "Display Name" ), profile->nickname, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* birthday */
+ field = purple_request_field_string_new( "bday", _( "Birthday" ), profile->birthday, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* gender */
+ field = purple_request_field_choice_new( "male", _( "Gender" ), ( profile->male ) ? 1 : 0 );
+ purple_request_field_choice_add( field, _( "Female" ) ); /* 0 */
+ purple_request_field_choice_add( field, _( "Male" ) ); /* 1 */
+ purple_request_field_group_add_field( group, field );
+
+ /* hidden */
+ field = purple_request_field_bool_new( "hidden", _( "Hide my number" ), profile->hidden );
+ purple_request_field_group_add_field( group, field );
+
+ /* title */
+ field = purple_request_field_string_new( "title", _( "Title" ), profile->title, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* first name */
+ field = purple_request_field_string_new( "firstname", _( "First Name" ), profile->firstname, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* last name */
+ field = purple_request_field_string_new( "lastname", _( "Last Name" ), profile->lastname, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* email */
+ field = purple_request_field_string_new( "email", _( "Email" ), profile->email, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* mobile number */
+ field = purple_request_field_string_new( "mobilenumber", _( "Mobile Number" ), profile->mobilenr, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* (reference: "libpurple/request.h") */
+ purple_request_fields( gc, _( "Profile" ), _( "Update your Profile" ), _( "Here you can update your MXit profile" ), fields, _( "Set" ),
+ G_CALLBACK( mxit_cb_set_profile ), _( "Cancel" ), NULL, purple_connection_get_account( gc ), NULL, NULL, gc );
+}
+
+
+/*------------------------------------------------------------------------
+ * Display the current splash-screen, or a notification pop-up if one is not available.
+ *
+ * @param action The action object
+ */
+static void mxit_cb_action_splash( PurplePluginAction* action )
+{
+ PurpleConnection* gc = (PurpleConnection*) action->context;
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+
+ if ( splash_current( session ) != NULL )
+ splash_display( session );
+ else
+ mxit_popup( PURPLE_NOTIFY_MSG_INFO, _( "View Splash" ), _( "There is no splash-screen currently available" ) );
+}
+
+
+/*------------------------------------------------------------------------
+ * Display info about the plugin.
+ *
+ * @param action The action object
+ */
+static void mxit_cb_action_about( PurplePluginAction* action )
+{
+ char version[256];
+
+ g_snprintf( version, sizeof( version ), "MXit libPurple Plugin v%s\n"
+ "MXit Client Protocol v%s\n\n"
+ "Author:\nPieter Loubser\n\n"
+ "Contributors:\nAndrew Victor\n\n"
+ "Testers:\nBraeme Le Roux\n\n",
+ MXIT_PLUGIN_VERSION, MXIT_CP_RELEASE );
+
+ mxit_popup( PURPLE_NOTIFY_MSG_INFO, _( "About" ), version );
+}
+
+
+/*------------------------------------------------------------------------
+ * Associate actions with the MXit plugin.
+ *
+ * @param plugin The MXit protocol plugin
+ * @param context The connection context (if available)
+ * @return The list of plugin actions
+ */
+GList* mxit_actions( PurplePlugin* plugin, gpointer context )
+{
+ PurplePluginAction* action = NULL;
+ GList* m = NULL;
+
+ /* display / change mood */
+ action = purple_plugin_action_new( _( "Change Mood..." ), mxit_cb_action_mood );
+ m = g_list_append( m, action );
+
+ /* display / change profile */
+ action = purple_plugin_action_new( _( "Change Profile..." ), mxit_cb_action_profile );
+ m = g_list_append( m, action );
+
+ /* display splash-screen */
+ action = purple_plugin_action_new( _( "View Splash..." ), mxit_cb_action_splash );
+ m = g_list_append( m, action );
+
+ /* display plugin version */
+ action = purple_plugin_action_new( _( "About..." ), mxit_cb_action_about );
+ m = g_list_append( m, action );
+
+ return m;
+}
+
diff --git a/libpurple/protocols/mxit/actions.h b/libpurple/protocols/mxit/actions.h
new file mode 100644
index 0000000000..0157e17c96
--- /dev/null
+++ b/libpurple/protocols/mxit/actions.h
@@ -0,0 +1,34 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- handle MXit plugin actions --
+ *
+ * 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
+ */
+
+#ifndef _MXIT_ACTIONS_H_
+#define _MXIT_ACTIONS_H_
+
+
+/* callbacks */
+GList* mxit_actions( PurplePlugin* plugin, gpointer context );
+
+
+#endif /* _MXIT_ACTIONS_H_ */
diff --git a/libpurple/protocols/mxit/aes.c b/libpurple/protocols/mxit/aes.c
new file mode 100644
index 0000000000..2a2702b1a8
--- /dev/null
+++ b/libpurple/protocols/mxit/aes.c
@@ -0,0 +1,405 @@
+
+// advanced encryption standard
+// author: karl malbrain, malbrain@yahoo.com
+
+/*
+This work, including the source code, documentation
+and related data, is placed into the public domain.
+
+The orginal author is Karl Malbrain.
+
+THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY
+OF ANY KIND, NOT EVEN THE IMPLIED WARRANTY OF
+MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE,
+ASSUMES _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE
+RESULTING FROM THE USE, MODIFICATION, OR
+REDISTRIBUTION OF THIS SOFTWARE.
+*/
+
+#include <string.h>
+#include <memory.h>
+
+#include "aes.h"
+
+// AES only supports Nb=4
+#define Nb 4 // number of columns in the state & expanded key
+
+#define Nk 4 // number of columns in a key
+#define Nr 10 // number of rounds in encryption
+
+static uchar Sbox[256] = { // forward s-box
+0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
+0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
+0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
+0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
+0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
+0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
+0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
+0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
+0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
+0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
+0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
+0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
+0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
+0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16};
+
+static uchar InvSbox[256] = { // inverse s-box
+0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
+0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
+0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
+0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
+0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
+0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
+0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
+0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
+0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
+0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
+0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
+0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
+0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
+0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
+0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
+0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d};
+
+// combined Xtimes2[Sbox[]]
+static uchar Xtime2Sbox[256] = {
+0xc6, 0xf8, 0xee, 0xf6, 0xff, 0xd6, 0xde, 0x91, 0x60, 0x02, 0xce, 0x56, 0xe7, 0xb5, 0x4d, 0xec,
+0x8f, 0x1f, 0x89, 0xfa, 0xef, 0xb2, 0x8e, 0xfb, 0x41, 0xb3, 0x5f, 0x45, 0x23, 0x53, 0xe4, 0x9b,
+0x75, 0xe1, 0x3d, 0x4c, 0x6c, 0x7e, 0xf5, 0x83, 0x68, 0x51, 0xd1, 0xf9, 0xe2, 0xab, 0x62, 0x2a,
+0x08, 0x95, 0x46, 0x9d, 0x30, 0x37, 0x0a, 0x2f, 0x0e, 0x24, 0x1b, 0xdf, 0xcd, 0x4e, 0x7f, 0xea,
+0x12, 0x1d, 0x58, 0x34, 0x36, 0xdc, 0xb4, 0x5b, 0xa4, 0x76, 0xb7, 0x7d, 0x52, 0xdd, 0x5e, 0x13,
+0xa6, 0xb9, 0x00, 0xc1, 0x40, 0xe3, 0x79, 0xb6, 0xd4, 0x8d, 0x67, 0x72, 0x94, 0x98, 0xb0, 0x85,
+0xbb, 0xc5, 0x4f, 0xed, 0x86, 0x9a, 0x66, 0x11, 0x8a, 0xe9, 0x04, 0xfe, 0xa0, 0x78, 0x25, 0x4b,
+0xa2, 0x5d, 0x80, 0x05, 0x3f, 0x21, 0x70, 0xf1, 0x63, 0x77, 0xaf, 0x42, 0x20, 0xe5, 0xfd, 0xbf,
+0x81, 0x18, 0x26, 0xc3, 0xbe, 0x35, 0x88, 0x2e, 0x93, 0x55, 0xfc, 0x7a, 0xc8, 0xba, 0x32, 0xe6,
+0xc0, 0x19, 0x9e, 0xa3, 0x44, 0x54, 0x3b, 0x0b, 0x8c, 0xc7, 0x6b, 0x28, 0xa7, 0xbc, 0x16, 0xad,
+0xdb, 0x64, 0x74, 0x14, 0x92, 0x0c, 0x48, 0xb8, 0x9f, 0xbd, 0x43, 0xc4, 0x39, 0x31, 0xd3, 0xf2,
+0xd5, 0x8b, 0x6e, 0xda, 0x01, 0xb1, 0x9c, 0x49, 0xd8, 0xac, 0xf3, 0xcf, 0xca, 0xf4, 0x47, 0x10,
+0x6f, 0xf0, 0x4a, 0x5c, 0x38, 0x57, 0x73, 0x97, 0xcb, 0xa1, 0xe8, 0x3e, 0x96, 0x61, 0x0d, 0x0f,
+0xe0, 0x7c, 0x71, 0xcc, 0x90, 0x06, 0xf7, 0x1c, 0xc2, 0x6a, 0xae, 0x69, 0x17, 0x99, 0x3a, 0x27,
+0xd9, 0xeb, 0x2b, 0x22, 0xd2, 0xa9, 0x07, 0x33, 0x2d, 0x3c, 0x15, 0xc9, 0x87, 0xaa, 0x50, 0xa5,
+0x03, 0x59, 0x09, 0x1a, 0x65, 0xd7, 0x84, 0xd0, 0x82, 0x29, 0x5a, 0x1e, 0x7b, 0xa8, 0x6d, 0x2c
+};
+
+// combined Xtimes3[Sbox[]]
+static uchar Xtime3Sbox[256] = {
+0xa5, 0x84, 0x99, 0x8d, 0x0d, 0xbd, 0xb1, 0x54, 0x50, 0x03, 0xa9, 0x7d, 0x19, 0x62, 0xe6, 0x9a,
+0x45, 0x9d, 0x40, 0x87, 0x15, 0xeb, 0xc9, 0x0b, 0xec, 0x67, 0xfd, 0xea, 0xbf, 0xf7, 0x96, 0x5b,
+0xc2, 0x1c, 0xae, 0x6a, 0x5a, 0x41, 0x02, 0x4f, 0x5c, 0xf4, 0x34, 0x08, 0x93, 0x73, 0x53, 0x3f,
+0x0c, 0x52, 0x65, 0x5e, 0x28, 0xa1, 0x0f, 0xb5, 0x09, 0x36, 0x9b, 0x3d, 0x26, 0x69, 0xcd, 0x9f,
+0x1b, 0x9e, 0x74, 0x2e, 0x2d, 0xb2, 0xee, 0xfb, 0xf6, 0x4d, 0x61, 0xce, 0x7b, 0x3e, 0x71, 0x97,
+0xf5, 0x68, 0x00, 0x2c, 0x60, 0x1f, 0xc8, 0xed, 0xbe, 0x46, 0xd9, 0x4b, 0xde, 0xd4, 0xe8, 0x4a,
+0x6b, 0x2a, 0xe5, 0x16, 0xc5, 0xd7, 0x55, 0x94, 0xcf, 0x10, 0x06, 0x81, 0xf0, 0x44, 0xba, 0xe3,
+0xf3, 0xfe, 0xc0, 0x8a, 0xad, 0xbc, 0x48, 0x04, 0xdf, 0xc1, 0x75, 0x63, 0x30, 0x1a, 0x0e, 0x6d,
+0x4c, 0x14, 0x35, 0x2f, 0xe1, 0xa2, 0xcc, 0x39, 0x57, 0xf2, 0x82, 0x47, 0xac, 0xe7, 0x2b, 0x95,
+0xa0, 0x98, 0xd1, 0x7f, 0x66, 0x7e, 0xab, 0x83, 0xca, 0x29, 0xd3, 0x3c, 0x79, 0xe2, 0x1d, 0x76,
+0x3b, 0x56, 0x4e, 0x1e, 0xdb, 0x0a, 0x6c, 0xe4, 0x5d, 0x6e, 0xef, 0xa6, 0xa8, 0xa4, 0x37, 0x8b,
+0x32, 0x43, 0x59, 0xb7, 0x8c, 0x64, 0xd2, 0xe0, 0xb4, 0xfa, 0x07, 0x25, 0xaf, 0x8e, 0xe9, 0x18,
+0xd5, 0x88, 0x6f, 0x72, 0x24, 0xf1, 0xc7, 0x51, 0x23, 0x7c, 0x9c, 0x21, 0xdd, 0xdc, 0x86, 0x85,
+0x90, 0x42, 0xc4, 0xaa, 0xd8, 0x05, 0x01, 0x12, 0xa3, 0x5f, 0xf9, 0xd0, 0x91, 0x58, 0x27, 0xb9,
+0x38, 0x13, 0xb3, 0x33, 0xbb, 0x70, 0x89, 0xa7, 0xb6, 0x22, 0x92, 0x20, 0x49, 0xff, 0x78, 0x7a,
+0x8f, 0xf8, 0x80, 0x17, 0xda, 0x31, 0xc6, 0xb8, 0xc3, 0xb0, 0x77, 0x11, 0xcb, 0xfc, 0xd6, 0x3a
+};
+
+// modular multiplication tables
+// based on:
+
+// Xtime2[x] = (x & 0x80 ? 0x1b : 0) ^ (x + x)
+// Xtime3[x] = x^Xtime2[x];
+
+#if 0
+static uchar Xtime2[256] = {
+0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e,
+0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e,
+0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e,
+0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e,
+0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e,
+0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe,
+0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde,
+0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe,
+0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05,
+0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25,
+0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45,
+0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65,
+0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85,
+0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5,
+0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5,
+0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5};
+#endif
+
+static uchar Xtime9[256] = {
+0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
+0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7,
+0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c,
+0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc,
+0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01,
+0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91,
+0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a,
+0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa,
+0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b,
+0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b,
+0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0,
+0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30,
+0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed,
+0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d,
+0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6,
+0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46};
+
+static uchar XtimeB[256] = {
+0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69,
+0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9,
+0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12,
+0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2,
+0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f,
+0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f,
+0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4,
+0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54,
+0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e,
+0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e,
+0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5,
+0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55,
+0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68,
+0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8,
+0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13,
+0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3};
+
+static uchar XtimeD[256] = {
+0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b,
+0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b,
+0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0,
+0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20,
+0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26,
+0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6,
+0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d,
+0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d,
+0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91,
+0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41,
+0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a,
+0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa,
+0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc,
+0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c,
+0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47,
+0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97};
+
+static uchar XtimeE[256] = {
+0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a,
+0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba,
+0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81,
+0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61,
+0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7,
+0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17,
+0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c,
+0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc,
+0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b,
+0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb,
+0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0,
+0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20,
+0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6,
+0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56,
+0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d,
+0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d};
+
+// exchanges columns in each of 4 rows
+// row0 - unchanged, row1- shifted left 1,
+// row2 - shifted left 2 and row3 - shifted left 3
+static void ShiftRows (uchar *state)
+{
+uchar tmp;
+
+ // just substitute row 0
+ state[0] = Sbox[state[0]], state[4] = Sbox[state[4]];
+ state[8] = Sbox[state[8]], state[12] = Sbox[state[12]];
+
+ // rotate row 1
+ tmp = Sbox[state[1]], state[1] = Sbox[state[5]];
+ state[5] = Sbox[state[9]], state[9] = Sbox[state[13]], state[13] = tmp;
+
+ // rotate row 2
+ tmp = Sbox[state[2]], state[2] = Sbox[state[10]], state[10] = tmp;
+ tmp = Sbox[state[6]], state[6] = Sbox[state[14]], state[14] = tmp;
+
+ // rotate row 3
+ tmp = Sbox[state[15]], state[15] = Sbox[state[11]];
+ state[11] = Sbox[state[7]], state[7] = Sbox[state[3]], state[3] = tmp;
+}
+
+// restores columns in each of 4 rows
+// row0 - unchanged, row1- shifted right 1,
+// row2 - shifted right 2 and row3 - shifted right 3
+static void InvShiftRows (uchar *state)
+{
+uchar tmp;
+
+ // restore row 0
+ state[0] = InvSbox[state[0]], state[4] = InvSbox[state[4]];
+ state[8] = InvSbox[state[8]], state[12] = InvSbox[state[12]];
+
+ // restore row 1
+ tmp = InvSbox[state[13]], state[13] = InvSbox[state[9]];
+ state[9] = InvSbox[state[5]], state[5] = InvSbox[state[1]], state[1] = tmp;
+
+ // restore row 2
+ tmp = InvSbox[state[2]], state[2] = InvSbox[state[10]], state[10] = tmp;
+ tmp = InvSbox[state[6]], state[6] = InvSbox[state[14]], state[14] = tmp;
+
+ // restore row 3
+ tmp = InvSbox[state[3]], state[3] = InvSbox[state[7]];
+ state[7] = InvSbox[state[11]], state[11] = InvSbox[state[15]], state[15] = tmp;
+}
+
+// recombine and mix each row in a column
+static void MixSubColumns (uchar *state)
+{
+uchar tmp[4 * Nb];
+
+ // mixing column 0
+ tmp[0] = Xtime2Sbox[state[0]] ^ Xtime3Sbox[state[5]] ^ Sbox[state[10]] ^ Sbox[state[15]];
+ tmp[1] = Sbox[state[0]] ^ Xtime2Sbox[state[5]] ^ Xtime3Sbox[state[10]] ^ Sbox[state[15]];
+ tmp[2] = Sbox[state[0]] ^ Sbox[state[5]] ^ Xtime2Sbox[state[10]] ^ Xtime3Sbox[state[15]];
+ tmp[3] = Xtime3Sbox[state[0]] ^ Sbox[state[5]] ^ Sbox[state[10]] ^ Xtime2Sbox[state[15]];
+
+ // mixing column 1
+ tmp[4] = Xtime2Sbox[state[4]] ^ Xtime3Sbox[state[9]] ^ Sbox[state[14]] ^ Sbox[state[3]];
+ tmp[5] = Sbox[state[4]] ^ Xtime2Sbox[state[9]] ^ Xtime3Sbox[state[14]] ^ Sbox[state[3]];
+ tmp[6] = Sbox[state[4]] ^ Sbox[state[9]] ^ Xtime2Sbox[state[14]] ^ Xtime3Sbox[state[3]];
+ tmp[7] = Xtime3Sbox[state[4]] ^ Sbox[state[9]] ^ Sbox[state[14]] ^ Xtime2Sbox[state[3]];
+
+ // mixing column 2
+ tmp[8] = Xtime2Sbox[state[8]] ^ Xtime3Sbox[state[13]] ^ Sbox[state[2]] ^ Sbox[state[7]];
+ tmp[9] = Sbox[state[8]] ^ Xtime2Sbox[state[13]] ^ Xtime3Sbox[state[2]] ^ Sbox[state[7]];
+ tmp[10] = Sbox[state[8]] ^ Sbox[state[13]] ^ Xtime2Sbox[state[2]] ^ Xtime3Sbox[state[7]];
+ tmp[11] = Xtime3Sbox[state[8]] ^ Sbox[state[13]] ^ Sbox[state[2]] ^ Xtime2Sbox[state[7]];
+
+ // mixing column 3
+ tmp[12] = Xtime2Sbox[state[12]] ^ Xtime3Sbox[state[1]] ^ Sbox[state[6]] ^ Sbox[state[11]];
+ tmp[13] = Sbox[state[12]] ^ Xtime2Sbox[state[1]] ^ Xtime3Sbox[state[6]] ^ Sbox[state[11]];
+ tmp[14] = Sbox[state[12]] ^ Sbox[state[1]] ^ Xtime2Sbox[state[6]] ^ Xtime3Sbox[state[11]];
+ tmp[15] = Xtime3Sbox[state[12]] ^ Sbox[state[1]] ^ Sbox[state[6]] ^ Xtime2Sbox[state[11]];
+
+ memcpy (state, tmp, sizeof(tmp));
+}
+
+// restore and un-mix each row in a column
+static void InvMixSubColumns (uchar *state)
+{
+uchar tmp[4 * Nb];
+int i;
+
+ // restore column 0
+ tmp[0] = XtimeE[state[0]] ^ XtimeB[state[1]] ^ XtimeD[state[2]] ^ Xtime9[state[3]];
+ tmp[5] = Xtime9[state[0]] ^ XtimeE[state[1]] ^ XtimeB[state[2]] ^ XtimeD[state[3]];
+ tmp[10] = XtimeD[state[0]] ^ Xtime9[state[1]] ^ XtimeE[state[2]] ^ XtimeB[state[3]];
+ tmp[15] = XtimeB[state[0]] ^ XtimeD[state[1]] ^ Xtime9[state[2]] ^ XtimeE[state[3]];
+
+ // restore column 1
+ tmp[4] = XtimeE[state[4]] ^ XtimeB[state[5]] ^ XtimeD[state[6]] ^ Xtime9[state[7]];
+ tmp[9] = Xtime9[state[4]] ^ XtimeE[state[5]] ^ XtimeB[state[6]] ^ XtimeD[state[7]];
+ tmp[14] = XtimeD[state[4]] ^ Xtime9[state[5]] ^ XtimeE[state[6]] ^ XtimeB[state[7]];
+ tmp[3] = XtimeB[state[4]] ^ XtimeD[state[5]] ^ Xtime9[state[6]] ^ XtimeE[state[7]];
+
+ // restore column 2
+ tmp[8] = XtimeE[state[8]] ^ XtimeB[state[9]] ^ XtimeD[state[10]] ^ Xtime9[state[11]];
+ tmp[13] = Xtime9[state[8]] ^ XtimeE[state[9]] ^ XtimeB[state[10]] ^ XtimeD[state[11]];
+ tmp[2] = XtimeD[state[8]] ^ Xtime9[state[9]] ^ XtimeE[state[10]] ^ XtimeB[state[11]];
+ tmp[7] = XtimeB[state[8]] ^ XtimeD[state[9]] ^ Xtime9[state[10]] ^ XtimeE[state[11]];
+
+ // restore column 3
+ tmp[12] = XtimeE[state[12]] ^ XtimeB[state[13]] ^ XtimeD[state[14]] ^ Xtime9[state[15]];
+ tmp[1] = Xtime9[state[12]] ^ XtimeE[state[13]] ^ XtimeB[state[14]] ^ XtimeD[state[15]];
+ tmp[6] = XtimeD[state[12]] ^ Xtime9[state[13]] ^ XtimeE[state[14]] ^ XtimeB[state[15]];
+ tmp[11] = XtimeB[state[12]] ^ XtimeD[state[13]] ^ Xtime9[state[14]] ^ XtimeE[state[15]];
+
+ for( i=0; i < 4 * Nb; i++ )
+ state[i] = InvSbox[tmp[i]];
+}
+
+// encrypt/decrypt columns of the key
+// n.b. you can replace this with
+// byte-wise xor if you wish.
+
+static void AddRoundKey (unsigned *state, unsigned *key)
+{
+int idx;
+
+ for( idx = 0; idx < 4; idx++ )
+ state[idx] ^= key[idx];
+}
+
+static uchar Rcon[11] = {
+0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36};
+
+// produce Nb bytes for each round
+void ExpandKey (uchar *key, uchar *expkey)
+{
+uchar tmp0, tmp1, tmp2, tmp3, tmp4;
+unsigned idx;
+
+ memcpy (expkey, key, Nk * 4);
+
+ for( idx = Nk; idx < Nb * (Nr + 1); idx++ ) {
+ tmp0 = expkey[4*idx - 4];
+ tmp1 = expkey[4*idx - 3];
+ tmp2 = expkey[4*idx - 2];
+ tmp3 = expkey[4*idx - 1];
+ if( !(idx % Nk) ) {
+ tmp4 = tmp3;
+ tmp3 = Sbox[tmp0];
+ tmp0 = Sbox[tmp1] ^ Rcon[idx/Nk];
+ tmp1 = Sbox[tmp2];
+ tmp2 = Sbox[tmp4];
+ } else if( Nk > 6 && idx % Nk == 4 ) {
+ tmp0 = Sbox[tmp0];
+ tmp1 = Sbox[tmp1];
+ tmp2 = Sbox[tmp2];
+ tmp3 = Sbox[tmp3];
+ }
+
+ expkey[4*idx+0] = expkey[4*idx - 4*Nk + 0] ^ tmp0;
+ expkey[4*idx+1] = expkey[4*idx - 4*Nk + 1] ^ tmp1;
+ expkey[4*idx+2] = expkey[4*idx - 4*Nk + 2] ^ tmp2;
+ expkey[4*idx+3] = expkey[4*idx - 4*Nk + 3] ^ tmp3;
+ }
+}
+
+// encrypt one 128 bit block
+void Encrypt (uchar *in, uchar *expkey, uchar *out)
+{
+uchar state[Nb * 4];
+unsigned round;
+
+ memcpy (state, in, Nb * 4);
+ AddRoundKey ((unsigned *)state, (unsigned *)expkey);
+
+ for( round = 1; round < Nr + 1; round++ ) {
+ if( round < Nr )
+ MixSubColumns (state);
+ else
+ ShiftRows (state);
+
+ AddRoundKey ((unsigned *)state, (unsigned *)expkey + round * Nb);
+ }
+
+ memcpy (out, state, sizeof(state));
+}
+
+void Decrypt (uchar *in, uchar *expkey, uchar *out)
+{
+uchar state[Nb * 4];
+unsigned round;
+
+ memcpy (state, in, sizeof(state));
+
+ AddRoundKey ((unsigned *)state, (unsigned *)expkey + Nr * Nb);
+ InvShiftRows(state);
+
+ for( round = Nr; round--; )
+ {
+ AddRoundKey ((unsigned *)state, (unsigned *)expkey + round * Nb);
+ if( round )
+ InvMixSubColumns (state);
+ }
+
+ memcpy (out, state, sizeof(state));
+}
diff --git a/libpurple/protocols/mxit/aes.h b/libpurple/protocols/mxit/aes.h
new file mode 100644
index 0000000000..831a30d3c9
--- /dev/null
+++ b/libpurple/protocols/mxit/aes.h
@@ -0,0 +1,39 @@
+// advanced encryption standard
+// author: karl malbrain, malbrain@yahoo.com
+
+/*
+This work, including the source code, documentation
+and related data, is placed into the public domain.
+
+The orginal author is Karl Malbrain.
+
+THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY
+OF ANY KIND, NOT EVEN THE IMPLIED WARRANTY OF
+MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE,
+ASSUMES _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE
+RESULTING FROM THE USE, MODIFICATION, OR
+REDISTRIBUTION OF THIS SOFTWARE.
+*/
+
+
+#ifndef AES_MALBRAIN
+#define AES_MALBRAIN
+
+
+// AES only supports Nb=4
+#define Nb 4 // number of columns in the state & expanded key
+
+#define Nk 4 // number of columns in a key
+#define Nr 10 // number of rounds in encryption
+
+
+typedef unsigned char uchar;
+
+
+void ExpandKey (uchar *key, uchar *expkey);
+void Encrypt (uchar *in, uchar *expkey, uchar *out);
+void Decrypt (uchar *in, uchar *expkey, uchar *out);
+
+
+#endif /* AES_MALBRAIN */
+
diff --git a/libpurple/protocols/mxit/chunk.c b/libpurple/protocols/mxit/chunk.c
new file mode 100644
index 0000000000..5d0cfe1603
--- /dev/null
+++ b/libpurple/protocols/mxit/chunk.c
@@ -0,0 +1,659 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- handle chunked data (multimedia messages) --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "purple.h"
+#include "protocol.h"
+#include "mxit.h"
+#include "chunk.h"
+#include "filexfer.h"
+
+
+/*========================================================================================================================
+ * Data-Type encoding
+ */
+
+#if 0
+#include <byteswap.h>
+#if (__BYTE_ORDER == __BIG_ENDIAN)
+#define SWAP_64(x) (x)
+#else
+#define SWAP_64(x) bswap_64(x)
+#endif
+#endif
+
+/*------------------------------------------------------------------------
+ * Encode a single byte in the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The byte
+ * @return The number of bytes added.
+ */
+static int add_int8( char* chunkdata, char value )
+{
+ *chunkdata = value;
+
+ return sizeof( char );
+}
+
+/*------------------------------------------------------------------------
+ * Encode a 16-bit value in the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The 16-bit value
+ * @return The number of bytes added.
+ */
+static int add_int16( char* chunkdata, short value )
+{
+ value = htons( value ); /* network byte-order */
+ memcpy( chunkdata, &value, sizeof( short ) );
+
+ return sizeof( short );
+}
+
+/*------------------------------------------------------------------------
+ * Encode a 32-bit value in the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The 32-bit value
+ * @return The number of bytes added.
+ */
+static int add_int32( char* chunkdata, int value )
+{
+ value = htonl( value ); /* network byte-order */
+ memcpy( chunkdata, &value, sizeof( int ) );
+
+ return sizeof( int );
+}
+
+#if 0
+/*------------------------------------------------------------------------
+ * Encode a 64-bit value in the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The 64-bit value
+ * @return The number of bytes added.
+ */
+static int add_int64( char* chunkdata, int64_t value )
+{
+ value = SWAP_64( value ); /* network byte-order */
+ memcpy( chunkdata, &value, sizeof( int64_t ) );
+
+ return sizeof( int64_t );
+}
+#endif
+
+/*------------------------------------------------------------------------
+ * Encode a block of data in the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param data The data to add
+ * @param datalen The length of the data to add
+ * @return The number of bytes added.
+ */
+static int add_data( char* chunkdata, const char* data, int datalen )
+{
+ memcpy( chunkdata, data, datalen );
+
+ return datalen;
+}
+
+/*------------------------------------------------------------------------
+ * Encode a string as UTF-8 in the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param str The string to encode
+ * @return The number of bytes in the string
+ */
+static int add_utf8_string( char* chunkdata, const char* str )
+{
+ int pos = 0;
+ size_t len = strlen( str );
+
+ /* utf8 string length [2 bytes] */
+ pos += add_int16( &chunkdata[pos], len );
+
+ /* utf8 string */
+ pos += add_data( &chunkdata[pos], str, len );
+
+ return pos;
+}
+
+
+/*========================================================================================================================
+ * Data-Type decoding
+ */
+
+/*------------------------------------------------------------------------
+ * Extract a single byte from the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The byte
+ * @return The number of bytes extracted.
+ */
+static int get_int8( const char* chunkdata, char* value )
+{
+ *value = *chunkdata;
+
+ return sizeof( char );
+}
+
+/*------------------------------------------------------------------------
+ * Extract a 16-bit value from the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The 16-bit value
+ * @return The number of bytes extracted
+ */
+static int get_int16( const char* chunkdata, short* value )
+{
+ *value = ntohs( *( (const short*) chunkdata ) ); /* host byte-order */
+
+ return sizeof( short );
+}
+
+/*------------------------------------------------------------------------
+ * Extract a 32-bit value from the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The 32-bit value
+ * @return The number of bytes extracted
+ */
+static int get_int32( const char* chunkdata, int* value )
+{
+ *value = ntohl( *( (const int*) chunkdata ) ); /* host byte-order */
+
+ return sizeof( int );
+}
+
+#if 0
+/*------------------------------------------------------------------------
+ * Extract a 64-bit value from the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param value The 64-bit value
+ * @return The number of bytes extracted
+ */
+static int get_int64( const char* chunkdata, int64_t* value )
+{
+ *value = SWAP_64( *( (const int64_t*) chunkdata ) ); /* host byte-order */
+
+ return sizeof( int64_t );
+}
+#endif
+
+/*------------------------------------------------------------------------
+ * Copy a block of data from the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param dest Where to store the extract data
+ * @param datalen The length of the data to extract
+ * @return The number of bytes extracted
+ */
+static int get_data( const char* chunkdata, char* dest, int datalen )
+{
+ memcpy( dest, chunkdata, datalen );
+
+ return datalen;
+}
+
+/*------------------------------------------------------------------------
+ * Extract a UTF-8 encoded string from the chunked data.
+ *
+ * @param chunkdata The chunked-data buffer
+ * @param str A pointer to extracted string. Must be g_free()'d.
+ * @return The number of bytes consumed
+ */
+static int get_utf8_string( const char* chunkdata, char* str, int maxstrlen )
+{
+ int pos = 0;
+ short len;
+ int skip = 0;
+
+ /* string length [2 bytes] */
+ pos += get_int16( &chunkdata[pos], &len );
+
+ if ( len > maxstrlen ) {
+ /* possible buffer overflow */
+ purple_debug_error( MXIT_PLUGIN_ID, "Buffer overflow detected (get_utf8_string)\n" );
+ skip = len - maxstrlen;
+ len = maxstrlen;
+ }
+
+ /* string data */
+ pos += get_data( &chunkdata[pos], str, len );
+ str[len] = '\0'; /* terminate string */
+
+ return pos + skip;
+}
+
+
+/*========================================================================================================================
+ * Chunked Data encoding
+ */
+
+/*------------------------------------------------------------------------
+ * Encode a "reject file" chunk. (Chunk type 7)
+ *
+ * @param chunkdata Chunked-data buffer
+ * @param fileid A unique ID that identifies this file
+ * @return The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_reject( char* chunkdata, const char* fileid )
+{
+ int pos = 0;
+
+ /* file id [8 bytes] */
+ pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN );
+
+ /* rejection reason [1 byte] */
+ pos += add_int8( &chunkdata[pos], REJECT_BY_USER );
+
+ /* rejection description [UTF-8 (optional)] */
+ pos += add_utf8_string( &chunkdata[pos], "" );
+
+ return pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encode a "get file" request chunk. (Chunk type 8)
+ *
+ * @param chunkdata Chunked-data buffer
+ * @param fileid A unique ID that identifies this file
+ * @param filesize The number of bytes to retrieve
+ * @param offset The start offset in the file
+ * @return The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_get( char* chunkdata, const char* fileid, int filesize, int offset )
+{
+ int pos = 0;
+
+ /* file id [8 bytes] */
+ pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN );
+
+ /* offset [4 bytes] */
+ pos += add_int32( &chunkdata[pos], offset );
+
+ /* length [4 bytes] */
+ pos += add_int32( &chunkdata[pos], filesize );
+
+ return pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encode a "received file" chunk. (Chunk type 9)
+ *
+ * @param chunkdata Chunked-data buffer
+ * @param fileid A unique ID that identifies this file
+ * @param status The status of the file transfer (see chunk.h)
+ * @return The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_received( char* chunkdata, const char* fileid, unsigned char status )
+{
+ int pos = 0;
+
+ /* file id [8 bytes] */
+ pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN );
+
+ /* status [1 byte] */
+ pos += add_int8( &chunkdata[pos], status );
+
+ return pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encode a "send file direct" chunk. (Chunk type 10)
+ *
+ * @param chunkdata Chunked-data buffer
+ * @param username The username of the recipient
+ * @param filename The name of the file being sent
+ * @param data The file contents
+ * @param datalen The size of the file contents
+ * @return The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_senddirect( char* chunkdata, const char* username, const char* filename, const unsigned char* data, int datalen )
+{
+ int pos = 0;
+ const char* mime = NULL;
+
+ /* data length [4 bytes] */
+ pos += add_int32( &chunkdata[pos], datalen );
+
+ /* number of username(s) [2 bytes] */
+ pos += add_int16( &chunkdata[pos], 1 );
+
+ /* username(s) [UTF-8] */
+ pos += add_utf8_string( &chunkdata[pos], username );
+
+ /* filename [UTF-8] */
+ pos += add_utf8_string( &chunkdata[pos], filename );
+
+ /* file mime type [UTF-8] */
+ mime = file_mime_type( filename, (const char*) data, datalen );
+ pos += add_utf8_string( &chunkdata[pos], mime );
+
+ /* human readable description [UTF-8 (optional)] */
+ pos += add_utf8_string( &chunkdata[pos], "" );
+
+ /* crc [4 bytes] (0 = optional) */
+ pos += add_int32( &chunkdata[pos], 0 );
+
+ /* the actual file data */
+ pos += add_data( &chunkdata[pos], (const char *) data, datalen );
+
+ return pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encode a "set avatar" chunk. (Chunk type 13)
+ *
+ * @param chunkdata Chunked-data buffer
+ * @param data The avatar data
+ * @param datalen The size of the avatar data
+ * @return The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_set_avatar( char* chunkdata, const unsigned char* data, int datalen )
+{
+ const char fileid[MXIT_CHUNK_FILEID_LEN];
+ int pos = 0;
+
+ /* id [8 bytes] */
+ memset( &fileid, 0, sizeof( fileid ) ); /* set to 0 for file upload */
+ pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN );
+
+ /* size [4 bytes] */
+ pos += add_int32( &chunkdata[pos], datalen );
+
+ /* crc [4 bytes] (0 = optional) */
+ pos += add_int32( &chunkdata[pos], 0 );
+
+ /* the actual file data */
+ pos += add_data( &chunkdata[pos], (const char *) data, datalen );
+
+ return pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encode a "get avatar" chunk. (Chunk type 14)
+ *
+ * @param chunkdata Chunked-data buffer
+ * @param mxitId The username who's avatar to download
+ * @param avatarId The Id of the avatar image (as string)
+ * @param imgsize The resolution of the avatar image
+ * @return The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_get_avatar( char* chunkdata, const char* mxitId, const char* avatarId, unsigned int imgsize )
+{
+ int pos = 0;
+
+ /* number of avatars [4 bytes] */
+ pos += add_int32( &chunkdata[pos], 1 );
+
+ /* username [UTF-8] */
+ pos += add_utf8_string( &chunkdata[pos], mxitId );
+
+ /* avatar id [UTF-8] */
+ pos += add_utf8_string( &chunkdata[pos], avatarId );
+
+ /* avatar format [UTF-8] */
+ pos += add_utf8_string( &chunkdata[pos], MXIT_AVATAR_TYPE );
+
+ /* avatar bit depth [1 byte] */
+ pos += add_int8( &chunkdata[pos], MXIT_AVATAR_BITDEPT );
+
+ /* number of sizes [2 bytes] */
+ pos += add_int16( &chunkdata[pos], 1 );
+
+ /* image size [4 bytes] */
+ pos += add_int32( &chunkdata[pos], imgsize );
+
+ return pos;
+}
+
+
+/*========================================================================================================================
+ * Chunked Data decoding
+ */
+
+/*------------------------------------------------------------------------
+ * Parse a received "offer file" chunk. (Chunk 6)
+ *
+ * @param chunkdata Chunked data buffer
+ * @param datalen The length of the chunked data
+ * @param offer Decoded offerfile information
+ */
+void mxit_chunk_parse_offer( char* chunkdata, int datalen, struct offerfile_chunk* offer )
+{
+ int pos = 0;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_offer (%i bytes)\n", datalen );
+
+ /* id [8 bytes] */
+ pos += get_data( &chunkdata[pos], offer->fileid, 8);
+
+ /* from username [UTF-8] */
+ pos += get_utf8_string( &chunkdata[pos], offer->username, sizeof( offer->username ) );
+ mxit_strip_domain( offer->username );
+
+ /* file size [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(offer->filesize) );
+
+ /* filename [UTF-8] */
+ pos += get_utf8_string( &chunkdata[pos], offer->filename, sizeof( offer->filename) );
+
+ /* mime type [UTF-8] */
+ /* not used by libPurple */
+
+ /* timestamp [8 bytes] */
+ /* not used by libPurple */
+
+ /* file description [UTF-8] */
+ /* not used by libPurple */
+
+ /* file alternative [UTF-8] */
+ /* not used by libPurple */
+
+ /* flags [4 bytes] */
+ /* not used by libPurple */
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse a received "get file" response chunk. (Chunk 8)
+ *
+ * @param chunkdata Chunked data buffer
+ * @param datalen The length of the chunked data
+ * @param offer Decoded getfile information
+ */
+void mxit_chunk_parse_get( char* chunkdata, int datalen, struct getfile_chunk* getfile )
+{
+ int pos = 0;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_file (%i bytes)\n", datalen );
+
+ /* id [8 bytes] */
+ pos += get_data( &chunkdata[pos], getfile->fileid, 8 );
+
+ /* offset [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(getfile->offset) );
+
+ /* file length [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(getfile->length) );
+
+ /* crc [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(getfile->crc) );
+
+ /* file data */
+ getfile->data = &chunkdata[pos];
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse a received splash screen chunk. (Chunk 2)
+ *
+ * @param chunkdata Chunked data buffer
+ * @param datalen The length of the chunked data
+ * @param splash Decoded splash image information
+ */
+static void mxit_chunk_parse_splash( char* chunkdata, int datalen, struct splash_chunk* splash )
+{
+ int pos = 0;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_splash (%i bytes)\n", datalen );
+
+ /* anchor [1 byte] */
+ pos += get_int8( &chunkdata[pos], &(splash->anchor) );
+
+ /* time to show [1 byte] */
+ pos += get_int8( &chunkdata[pos], &(splash->showtime) );
+
+ /* background color [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(splash->bgcolor) );
+
+ /* file data */
+ splash->data = &chunkdata[pos];
+
+ /* data length */
+ splash->datalen = datalen - pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse a received "custom resource" chunk. (Chunk 1)
+ *
+ * @param chunkdata Chunked data buffer
+ * @param datalen The length of the chunked data
+ * @param offer Decoded custom resource
+ */
+void mxit_chunk_parse_cr( char* chunkdata, int datalen, struct cr_chunk* cr )
+{
+ int pos = 0;
+ int chunklen = 0;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_cr (%i bytes)\n", datalen );
+
+ /* id [UTF-8] */
+ pos += get_utf8_string( &chunkdata[pos], cr->id, sizeof( cr->id ) );
+
+ /* handle [UTF-8] */
+ pos += get_utf8_string( &chunkdata[pos], cr->handle, sizeof( cr->handle ) );
+
+ /* operation [1 byte] */
+ pos += get_int8( &chunkdata[pos], &(cr->operation) );
+
+ /* chunk size [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &chunklen );
+
+ /* parse the resource chunks */
+ while ( chunklen > 0 ) {
+ struct raw_chunk* chunkhdr = ( struct raw_chunk * ) &chunkdata[pos];
+ chunkhdr->length = ntohl( chunkhdr->length ); /* host byte-order */
+
+ /* start of chunk data */
+ pos += sizeof( struct raw_chunk );
+
+ switch ( chunkhdr->type ) {
+ case CP_CHUNK_SPLASH : /* splash image */
+ {
+ struct splash_chunk* splash = g_new0( struct splash_chunk, 1 );
+
+ mxit_chunk_parse_splash( &chunkdata[pos], chunkhdr->length, splash );
+
+ cr->resources = g_list_append( cr->resources, splash );
+ break;
+ }
+ case CP_CHUNK_CLICK : /* splash click */
+ {
+ struct splash_click_chunk* click = g_new0( struct splash_click_chunk, 1 );
+
+ cr->resources = g_list_append( cr->resources, click );
+ break;
+ }
+ default:
+ purple_debug_info( MXIT_PLUGIN_ID, "Unsupported custom resource chunk received (%i)\n", chunkhdr->type );
+ }
+
+ /* skip over data to next resource chunk */
+ pos += chunkhdr->length;
+ chunklen -= ( sizeof( struct raw_chunk ) + chunkhdr->length );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse a received "get avatar" response chunk. (Chunk 14)
+ *
+ * @param chunkdata Chunked data buffer
+ * @param datalen The length of the chunked data
+ * @param avatar Decoded avatar information
+ */
+void mxit_chunk_parse_get_avatar( char* chunkdata, int datalen, struct getavatar_chunk* avatar )
+{
+ int pos = 0;
+ int numfiles = 0;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_get_avatar (%i bytes)\n", datalen );
+
+ /* number of files [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &numfiles );
+
+ if ( numfiles < 1 ) /* no data */
+ return;
+
+ /* mxitId [UTF-8 string] */
+ pos += get_utf8_string( &chunkdata[pos], avatar->mxitid, sizeof( avatar->mxitid ) );
+
+ /* avatar id [UTF-8 string] */
+ pos += get_utf8_string( &chunkdata[pos], avatar->avatarid, sizeof( avatar->avatarid ) );
+
+ /* format [UTF-8 string] */
+ pos += get_utf8_string( &chunkdata[pos], avatar->format, sizeof( avatar->format ) );
+
+ /* bit depth [1 byte] */
+ pos += get_int8( &chunkdata[pos], &(avatar->bitdepth) );
+
+ /* crc [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(avatar->crc) );
+
+ /* width [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(avatar->width) );
+
+ /* height [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(avatar->height) );
+
+ /* file length [4 bytes] */
+ pos += get_int32( &chunkdata[pos], &(avatar->length) );
+
+ /* file data */
+ avatar->data = &chunkdata[pos];
+}
diff --git a/libpurple/protocols/mxit/chunk.h b/libpurple/protocols/mxit/chunk.h
new file mode 100644
index 0000000000..b4247db66e
--- /dev/null
+++ b/libpurple/protocols/mxit/chunk.h
@@ -0,0 +1,140 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- handle chunked data (multimedia messages) --
+ *
+ * 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
+ */
+
+#ifndef _MXIT_CHUNK_H_
+#define _MXIT_CHUNK_H_
+
+
+#include "roster.h"
+
+
+#define MXIT_CHUNK_FILEID_LEN 8 /* bytes */
+
+/* Multimedia chunk types */
+#define CP_CHUNK_NONE 0x00 /* (0) no chunk */
+#define CP_CHUNK_CUSTOM 0x01 /* (1) custom resource */
+#define CP_CHUNK_SPLASH 0x02 /* (2) splash image */
+#define CP_CHUNK_CLICK 0x03 /* (3) splash click through */
+#define CP_CHUNK_OFFER 0x06 /* (6) offer file */
+#define CP_CHUNK_REJECT 0x07 /* (7) reject file */
+#define CP_CHUNK_GET 0x08 /* (8) get file */
+#define CP_CHUNK_RECIEVED 0x09 /* (9) received file */
+#define CP_CHUNK_DIRECT_SND 0x0A /* (10) send file direct */
+#define CP_CHUNK_DIRECT_FWD 0x0B /* (11) forward file direct */
+#define CP_CHUNK_SKIN 0x0C /* (12) MXit client skin */
+#define CP_CHUNK_SET_AVATAR 0x0D /* (13) set avatar */
+#define CP_CHUNK_GET_AVATAR 0x0E /* (14) get avatar */
+#define CP_CHUNK_END 0x7E /* (126) end */
+#define CP_CHUNK_EXT 0x7F /* (127) extended type */
+
+
+/* Custom Resource operations */
+#define CR_OP_UPDATE 0
+#define CR_OP_REMOVE 1
+
+/* File Received status */
+#define RECV_STATUS_SUCCESS 0
+#define RECV_STATUS_PARSE_FAIL 1
+#define RECV_STATUS_CANNOT_OPEN 8
+#define RECV_STATUS_BAD_CRC 9
+#define RECV_STATUS_BAD_ID 10
+
+/* File Reject status */
+#define REJECT_BY_USER 1
+#define REJECT_FILETYPE 2
+#define REJECT_NO_RESOURCES 3
+#define REJECT_BAD_RECIPIENT 4
+
+/*
+ * a Chunk header
+ */
+struct raw_chunk {
+ guint8 type;
+ guint32 length;
+ gchar data[0];
+} __attribute__ ((packed));
+
+struct offerfile_chunk {
+ char fileid[MXIT_CHUNK_FILEID_LEN];
+ char username[MXIT_CP_MAX_JID_LEN + 1];
+ int filesize;
+ char filename[FILENAME_MAX];
+};
+
+struct getfile_chunk {
+ char fileid[MXIT_CHUNK_FILEID_LEN];
+ int offset;
+ int length;
+ int crc;
+ char* data;
+};
+
+struct cr_chunk {
+ char id[64];
+ char handle[64];
+ char operation;
+ GList* resources;
+};
+
+struct splash_chunk {
+ char anchor;
+ char showtime;
+ int bgcolor;
+ char* data;
+ int datalen;
+};
+
+struct splash_click_chunk {
+ char reserved[1];
+};
+
+struct getavatar_chunk {
+ char mxitid[50];
+ char avatarid[64];
+ char format[16];
+ char bitdepth;
+ int crc;
+ int width;
+ int height;
+ int length;
+ char* data;
+};
+
+/* Encode chunk */
+int mxit_chunk_create_senddirect( char* chunkdata, const char* username, const char* filename, const unsigned char* data, int datalen );
+int mxit_chunk_create_reject( char* chunkdata, const char* fileid );
+int mxit_chunk_create_get( char* chunkdata, const char* fileid, int filesize, int offset );
+int mxit_chunk_create_received( char* chunkdata, const char* fileid, unsigned char status );
+int mxit_chunk_create_set_avatar( char* chunkdata, const unsigned char* data, int datalen );
+int mxit_chunk_create_get_avatar( char* chunkdata, const char* mxitId, const char* avatarId, unsigned int imgsize );
+
+/* Decode chunk */
+void mxit_chunk_parse_offer( char* chunkdata, int datalen, struct offerfile_chunk* offer );
+void mxit_chunk_parse_get( char* chunkdata, int datalen, struct getfile_chunk* getfile );
+void mxit_chunk_parse_cr( char* chunkdata, int datalen, struct cr_chunk* cr );
+void mxit_chunk_parse_get_avatar( char* chunkdata, int datalen, struct getavatar_chunk* avatar );
+
+#endif /* _MXIT_CHUNK_H_ */
+
diff --git a/libpurple/protocols/mxit/cipher.c b/libpurple/protocols/mxit/cipher.c
new file mode 100644
index 0000000000..46d4b6a337
--- /dev/null
+++ b/libpurple/protocols/mxit/cipher.c
@@ -0,0 +1,111 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- user password encryption --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "purple.h"
+
+#include "mxit.h"
+#include "cipher.h"
+#include "aes.h"
+
+
+/* password encryption */
+#define INITIAL_KEY "6170383452343567"
+#define SECRET_HEADER "<mxit/>"
+
+
+/*------------------------------------------------------------------------
+ * Pad the secret data using ISO10126 Padding.
+ *
+ * @param secret The data to pad (caller must ensure buffer has enough space for padding)
+ * @return The total number of 128-bit blocks used
+ */
+static int pad_secret_data( char* secret )
+{
+ int blocks = 0;
+ int passlen;
+ int padding;
+
+ passlen = strlen( secret );
+ blocks = ( passlen / 16 ) + 1;
+ padding = ( blocks * 16 ) - passlen;
+ secret[passlen] = 0x50;
+ secret[(blocks * 16) - 1] = padding;
+
+ return blocks;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encrypt the user's cleartext password using the AES 128-bit (ECB)
+ * encryption algorithm.
+ *
+ * @param session The MXit session object
+ * @return The encrypted & encoded password. Must be g_free'd when no longer needed.
+ */
+char* mxit_encrypt_password( struct MXitSession* session )
+{
+ char key[64];
+ char exkey[512];
+ char pass[64];
+ char encrypted[64];
+ char* base64;
+ int blocks;
+ int size;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_encrypt_password\n" );
+
+ memset( encrypted, 0x00, sizeof( encrypted ) );
+ memset( exkey, 0x00, sizeof( exkey ) );
+ memset( pass, 0x58, sizeof( pass ) );
+ pass[sizeof( pass ) - 1] = '\0';
+
+ /* build the custom AES encryption key */
+ strcpy( key, INITIAL_KEY );
+ memcpy( key, session->clientkey, strlen( session->clientkey ) );
+ ExpandKey( (unsigned char*) key, (unsigned char*) exkey );
+
+ /* build the custom data to be encrypted */
+ strcpy( pass, SECRET_HEADER );
+ strcat( pass, session->acc->password );
+
+ /* pad the secret data */
+ blocks = pad_secret_data( pass );
+ size = blocks * 16;
+
+ /* now encrypt the password. we encrypt each block separately (ECB mode) */
+ for ( i = 0; i < size; i += 16 )
+ Encrypt( (unsigned char*) pass + i, (unsigned char*) exkey, (unsigned char*) encrypted + i );
+
+ /* now base64 encode the encrypted password */
+ base64 = purple_base64_encode( (unsigned char*) encrypted, size );
+
+ return base64;
+}
+
diff --git a/libpurple/protocols/mxit/cipher.h b/libpurple/protocols/mxit/cipher.h
new file mode 100644
index 0000000000..188a0609b1
--- /dev/null
+++ b/libpurple/protocols/mxit/cipher.h
@@ -0,0 +1,36 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- user password encryption --
+ *
+ * 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
+ */
+
+#ifndef _MXIT_CIPHER_H_
+#define _MXIT_CIPHER_H_
+
+
+struct MXitSession;
+
+
+char* mxit_encrypt_password( struct MXitSession* session );
+
+
+#endif /* _MXIT_CIPHER_H_ */
diff --git a/libpurple/protocols/mxit/filexfer.c b/libpurple/protocols/mxit/filexfer.c
new file mode 100644
index 0000000000..a9254ff7fd
--- /dev/null
+++ b/libpurple/protocols/mxit/filexfer.c
@@ -0,0 +1,454 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- file transfers (sending and receiving) --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "purple.h"
+#include "protocol.h"
+#include "mxit.h"
+#include "chunk.h"
+#include "filexfer.h"
+
+
+#define MIME_TYPE_OCTETSTREAM "application/octet-stream"
+
+
+/* supported file mime types */
+static struct mime_type {
+ const char* magic;
+ const short magic_len;
+ const char* mime;
+} const mime_types[] = {
+ /* magic length mime */
+ /* images */ { "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8, "image/png" }, /* image png */
+ { "\xFF\xD8", 2, "image/jpeg" }, /* image jpeg */
+ { "\x3C\x3F\x78\x6D\x6C", 5, "image/svg+xml" }, /* image SVGansi */
+ { "\xEF\xBB\xBF", 3, "image/svg+xml" }, /* image SVGutf */
+ { "\xEF\xBB\xBF", 3, "image/svg+xml" }, /* image SVGZ */
+ /* mxit */ { "\x4d\x58\x4d", 3, "application/mxit-msgs" }, /* mxit message */
+ { "\x4d\x58\x44\x01", 4, "application/mxit-mood" }, /* mxit mood */
+ { "\x4d\x58\x45\x01", 4, "application/mxit-emo" }, /* mxit emoticon */
+ { "\x4d\x58\x46\x01", 4, "application/mxit-emof" }, /* mxit emoticon frame */
+ { "\x4d\x58\x53\x01", 4, "application/mxit-skin" }, /* mxit skin */
+ /* audio */ { "\x4d\x54\x68\x64", 4, "audio/midi" }, /* audio midi */
+ { "\x52\x49\x46\x46", 4, "audio/wav" }, /* audio wav */
+ { "\xFF\xF1", 2, "audio/aac" }, /* audio aac1 */
+ { "\xFF\xF9", 2, "audio/aac" }, /* audio aac2 */
+ { "\xFF", 1, "audio/mp3" }, /* audio mp3 */
+ { "\x23\x21\x41\x4D\x52\x0A", 6, "audio/amr" }, /* audio AMR */
+ { "\x23\x21\x41\x4D\x52\x2D\x57\x42", 8, "audio/amr-wb" }, /* audio AMR WB */
+ { "\x00\x00\x00", 3, "audio/mp4" }, /* audio mp4 */
+ { "\x2E\x73\x6E\x64", 4, "audio/au" } /* audio AU */
+};
+
+
+/*------------------------------------------------------------------------
+ * Return the MIME type matching the data file.
+ *
+ * @param filename The name of file
+ * @param buf The data
+ * @param buflen The length of the data
+ * @return A MIME type string
+ */
+const char* file_mime_type( const char* filename, const char* buf, int buflen )
+{
+ unsigned int i;
+
+ /* check for matching magic headers */
+ for ( i = 0; i < ARRAY_SIZE( mime_types ); i++ ) {
+
+ if ( buflen < mime_types[i].magic_len ) /* data is shorter than size of magic */
+ continue;
+
+ if ( memcmp( buf, mime_types[i].magic, mime_types[i].magic_len ) == 0 )
+ return mime_types[i].mime;
+ }
+
+ /* we did not find the MIME type, so return the default (application/octet-stream) */
+ return MIME_TYPE_OCTETSTREAM;
+}
+
+
+/*------------------------------------------------------------------------
+ * Cleanup and deallocate a MXit file transfer object
+ *
+ * @param xfer The file transfer object
+ */
+static void mxit_xfer_free( PurpleXfer* xfer )
+{
+ struct mxitxfer* mx = (struct mxitxfer*) xfer->data;;
+
+ if ( mx ) {
+ g_free( mx );
+ xfer->data = NULL;
+ }
+}
+
+
+/*========================================================================================================================
+ * File Transfer callbacks
+ */
+
+/*------------------------------------------------------------------------
+ * Initialise a new file transfer.
+ *
+ * @param xfer The file transfer object
+ */
+static void mxit_xfer_init( PurpleXfer* xfer )
+{
+ struct mxitxfer* mx = (struct mxitxfer*) xfer->data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_init\n" );
+
+ if ( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND ) {
+ /* we are trying to send a file to MXit */
+
+ if ( purple_xfer_get_size( xfer ) > CP_MAX_FILESIZE ) {
+ /* the file is too big */
+ purple_xfer_error( xfer->type, xfer->account, xfer->who, _( "The file you are trying to send is too large!" ) );
+ purple_xfer_cancel_local( xfer );
+ return;
+ }
+
+ /* start the file transfer */
+ purple_xfer_start( xfer, -1, NULL, 0 );
+ }
+ else {
+ /*
+ * we have just accepted a file transfer request from MXit. send a confirmation
+ * to the MXit server so that can send us the file
+ */
+ mxit_send_file_accept( mx->session, mx->fileid, purple_xfer_get_size( xfer ), 0 );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Start the file transfer.
+ *
+ * @param xfer The file transfer object
+ */
+static void mxit_xfer_start( PurpleXfer* xfer )
+{
+ unsigned char* buffer;
+ int size;
+ int wrote;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_start\n" );
+
+ if ( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND ) {
+ /*
+ * the user wants to send a file to one of his contacts. we need to create
+ * a buffer and copy the file data into memory and then we can send it to
+ * the contact. we will send the whole file with one go.
+ */
+ buffer = g_malloc( xfer->bytes_remaining );
+ size = fread( buffer, xfer->bytes_remaining, 1, xfer->dest_fp );
+
+ wrote = purple_xfer_write( xfer, buffer, xfer->bytes_remaining );
+ if ( wrote > 0 )
+ purple_xfer_set_bytes_sent( xfer, wrote );
+
+ /* free the buffer */
+ g_free( buffer );
+ buffer = NULL;
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * The file transfer has ended.
+ *
+ * @param xfer The file transfer object
+ */
+static void mxit_xfer_end( PurpleXfer* xfer )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_end\n" );
+
+ /* deallocate object */
+ mxit_xfer_free( xfer );
+}
+
+
+/*------------------------------------------------------------------------
+ * The file transfer (to a user) has been cancelled.
+ *
+ * @param xfer The file transfer object
+ */
+static void mxit_xfer_cancel_send( PurpleXfer* xfer )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_cancel_send\n" );
+
+ /* deallocate object */
+ mxit_xfer_free( xfer );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send the file data.
+ *
+ * @param buffer The data to sent
+ * @param size The length of the data to send
+ * @param xfer The file transfer object
+ * @return The amount of data actually sent
+ */
+static gssize mxit_xfer_write( const guchar* buffer, size_t size, PurpleXfer* xfer )
+{
+ struct mxitxfer* mx = (struct mxitxfer*) xfer->data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_write\n" );
+
+ if ( !mx ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "mxit_xfer_write: invalid internal mxit xfer data\n" );
+ return -1;
+ }
+ else if ( purple_xfer_get_type( xfer ) != PURPLE_XFER_SEND ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "mxit_xfer_write: wrong xfer type received\n" );
+ return -1;
+ }
+
+ /* create and send the packet to MXit */
+ mxit_send_file( mx->session, purple_xfer_get_remote_user( xfer ), purple_xfer_get_filename( xfer ), buffer, size );
+
+ /* the transfer is complete */
+ purple_xfer_set_completed( xfer, TRUE );
+
+ return size;
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has rejected a file offer from MXit.
+ *
+ * @param xfer The file transfer object
+ */
+static void mxit_xfer_request_denied( PurpleXfer* xfer )
+{
+ struct mxitxfer* mx = (struct mxitxfer*) xfer->data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_request_denied\n" );
+
+ /* send file reject packet to MXit server */
+ mxit_send_file_reject( mx->session, mx->fileid );
+
+ /* deallocate object */
+ mxit_xfer_free( xfer );
+}
+
+
+/*------------------------------------------------------------------------
+ * The file transfer (from MXit) has been cancelled.
+ */
+static void mxit_xfer_cancel_recv( PurpleXfer* xfer )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_cancel_recv\n" );
+
+ /* deallocate object */
+ mxit_xfer_free( xfer );
+}
+
+
+/*========================================================================================================================
+ * Callbacks from libPurple
+ */
+
+/*------------------------------------------------------------------------
+ * Indicate if file transfers are supported to this contact.
+ * For MXit file transfers are always supported.
+ *
+ * @param gc The connection object
+ * @param who The username of the contact
+ * @return TRUE if file transfers are supported
+ */
+gboolean mxit_xfer_enabled( PurpleConnection* gc, const char* who )
+{
+ return TRUE;
+}
+
+
+/*------------------------------------------------------------------------
+ * Create and initialize a new file transfer to a contact.
+ *
+ * @param gc The connection object
+ * @param who The username of the recipient
+ */
+PurpleXfer* mxit_xfer_new( PurpleConnection* gc, const char* who )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ PurpleXfer* xfer = NULL;
+ struct mxitxfer* mx = NULL;
+
+ /* (reference: "libpurple/ft.h") */
+ xfer = purple_xfer_new( session->acc, PURPLE_XFER_SEND, who );
+
+ /* create file info and attach it to the file transfer */
+ mx = g_new0( struct mxitxfer, 1 );
+ mx->session = session;
+ xfer->data = mx;
+
+ /* configure callbacks (reference: "libpurple/ft.h") */
+ purple_xfer_set_init_fnc( xfer, mxit_xfer_init );
+ purple_xfer_set_start_fnc( xfer, mxit_xfer_start );
+ purple_xfer_set_end_fnc( xfer, mxit_xfer_end );
+ purple_xfer_set_cancel_send_fnc( xfer, mxit_xfer_cancel_send );
+ purple_xfer_set_write_fnc( xfer, mxit_xfer_write );
+
+ return xfer;
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has initiated a file transfer to a contact.
+ *
+ * @param gc The connection object
+ * @param who The username of the contact
+ * @param filename The filename (is NULL if request has not been accepted yet)
+ */
+void mxit_xfer_tx( PurpleConnection* gc, const char* who, const char* filename )
+{
+ PurpleXfer *xfer = mxit_xfer_new( gc, who );
+
+ if ( filename )
+ purple_xfer_request_accepted( xfer, filename );
+ else
+ purple_xfer_request( xfer );
+}
+
+
+/*========================================================================================================================
+ * Calls from the MXit Protocol layer
+ */
+
+/*------------------------------------------------------------------------
+ * A file transfer offer has been received from the MXit server.
+ *
+ * @param session The MXit session object
+ * @param usermame The username of the sender
+ * @param filename The name of the file being offered
+ * @param filesize The size of the file being offered
+ * @param fileid A unique ID that identifies this file
+ */
+void mxit_xfer_rx_offer( struct MXitSession* session, const char* username, const char* filename, int filesize, const char* fileid )
+{
+ PurpleXfer* xfer = NULL;
+ struct mxitxfer* mx = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "File Offer: file=%s, from=%s, size=%i\n", filename, username, filesize );
+
+ xfer = purple_xfer_new( session->acc, PURPLE_XFER_RECEIVE, username );
+ if ( xfer ) {
+ /* create a new mxit xfer struct for internal use */
+ mx = g_new0( struct mxitxfer, 1 );
+ mx->session = session;
+ memcpy( mx->fileid, fileid, MXIT_CHUNK_FILEID_LEN );
+ xfer->data = mx;
+
+ purple_xfer_set_filename( xfer, filename );
+ if( filesize > 0 )
+ purple_xfer_set_size( xfer, filesize );
+
+ /* register file transfer callback functions */
+ purple_xfer_set_init_fnc( xfer, mxit_xfer_init );
+ purple_xfer_set_request_denied_fnc( xfer, mxit_xfer_request_denied );
+ purple_xfer_set_cancel_recv_fnc( xfer, mxit_xfer_cancel_recv );
+ purple_xfer_set_end_fnc( xfer, mxit_xfer_end );
+
+ /* give the request to the user to accept/deny */
+ purple_xfer_request( xfer );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the libPurple file-transfer object associated with a MXit transfer
+ *
+ * @param session The MXit session object
+ * @param fileid A unique ID that identifies this file
+ */
+static PurpleXfer* find_mxit_xfer( struct MXitSession* session, const char* fileid )
+{
+ GList* item = NULL;
+ PurpleXfer* xfer = NULL;
+
+ item = purple_xfers_get_all(); /* list of all active transfers */
+ while ( item ) {
+ xfer = item->data;
+
+ if ( xfer->account == session->acc ) {
+ /* transfer is associated with this MXit account */
+ struct mxitxfer* mx = xfer->data;
+
+ /* does the fileid match? */
+ if ( ( mx ) && ( memcmp( mx->fileid, fileid, MXIT_CHUNK_FILEID_LEN ) == 0 ) )
+ break;
+ }
+
+ item = g_list_next( item );
+ }
+
+ if ( item )
+ return item->data;
+ else
+ return NULL;
+}
+
+/*------------------------------------------------------------------------
+ * A file has been received from the MXit server.
+ *
+ * @param session The MXit session object
+ * @param fileid A unique ID that identifies this file
+ * @param data The file data
+ * @param datalen The size of the data
+ */
+void mxit_xfer_rx_file( struct MXitSession* session, const char* fileid, const char* data, int datalen )
+{
+ PurpleXfer* xfer = NULL;
+ struct mxitxfer* mx = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_rx_file: (size=%i)\n", datalen );
+
+ /* find the file-transfer object */
+ xfer = find_mxit_xfer( session, fileid );
+ if ( xfer ) {
+ mx = xfer->data;
+
+ /* this is the transfer we have been looking for */
+ purple_xfer_ref( xfer );
+ purple_xfer_start( xfer, -1, NULL, 0 );
+ fwrite( data, datalen, 1, xfer->dest_fp );
+ purple_xfer_unref( xfer );
+ purple_xfer_set_completed( xfer, TRUE );
+ purple_xfer_end( xfer );
+
+ /* inform MXit that file was successfully received */
+ mxit_send_file_received( session, fileid, RECV_STATUS_SUCCESS );
+ }
+ else {
+ /* file transfer not found */
+ mxit_send_file_received( session, fileid, RECV_STATUS_BAD_ID );
+ }
+}
diff --git a/libpurple/protocols/mxit/filexfer.h b/libpurple/protocols/mxit/filexfer.h
new file mode 100644
index 0000000000..4ec953d928
--- /dev/null
+++ b/libpurple/protocols/mxit/filexfer.h
@@ -0,0 +1,50 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- file transfers (sending and receiving) --
+ *
+ * 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
+ */
+
+#ifndef _MXIT_FILEXFER_H_
+#define _MXIT_FILEXFER_H_
+
+
+/*
+ * a MXit file transfer
+ */
+struct mxitxfer {
+ struct MXitSession* session;
+ char fileid[MXIT_CHUNK_FILEID_LEN];
+};
+
+const char* file_mime_type( const char* filename, const char* buf, int buflen );
+
+/* libPurple callbacks */
+gboolean mxit_xfer_enabled( PurpleConnection* gc, const char* who );
+void mxit_xfer_tx( PurpleConnection* gc, const char* who, const char* filename );
+PurpleXfer* mxit_xfer_new( PurpleConnection* gc, const char* who );
+
+/* MXit Protocol callbacks */
+void mxit_xfer_rx_offer( struct MXitSession* session, const char* username, const char* filename, int filesize, const char* fileid );
+void mxit_xfer_rx_file( struct MXitSession* session, const char* fileid, const char* data, int datalen );
+
+
+#endif /* _MXIT_FILEXFER_H_ */
diff --git a/libpurple/protocols/mxit/formcmds.c b/libpurple/protocols/mxit/formcmds.c
new file mode 100644
index 0000000000..cdb8258502
--- /dev/null
+++ b/libpurple/protocols/mxit/formcmds.c
@@ -0,0 +1,397 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit Forms & Commands --
+ *
+ * Andrew Victor <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 <string.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include "purple.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "markup.h"
+#include "formcmds.h"
+
+#undef MXIT_DEBUG_COMMANDS
+
+/*
+ * the MXit Command identifiers
+ */
+typedef enum
+{
+ MXIT_CMD_UNKNOWN = 0, /* Unknown command */
+ MXIT_CMD_CLRSCR, /* Clear screen (clrmsgscreen) */
+ MXIT_CMD_SENDSMS, /* Send SMS (sendsms) */
+ MXIT_CMD_REPLY, /* Reply (reply) */
+ MXIT_CMD_PLATREQ, /* Platform Request (platreq) */
+ MXIT_CMD_SELECTCONTACT, /* Select Contact (selc) */
+ MXIT_CMD_IMAGE /* Inline image (img) */
+} MXitCommandType;
+
+
+/*
+ * object for an inline image request with an URL
+ */
+struct ii_url_request
+{
+ struct RXMsgData* mx;
+ char* url;
+};
+
+
+/*------------------------------------------------------------------------
+ * Callback function invoked when an inline image request to a web 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 mxit_cb_ii_returned(PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message)
+{
+ struct ii_url_request* iireq = (struct ii_url_request*) user_data;
+ char* ii_data;
+ int* intptr = NULL;
+ int id;
+
+#ifdef MXIT_DEBUG_COMMANDS
+ purple_debug_info(MXIT_PLUGIN_ID, "Inline Image returned from %s\n", iireq->url);
+#endif
+
+ if (!url_text) {
+ /* no reply from the WAP site */
+ purple_debug_error(MXIT_PLUGIN_ID, "Error downloading Inline Image from %s.\n", iireq->url);
+ goto done;
+ }
+
+ /* lets first see if we dont have the inline image already in cache */
+ if (g_hash_table_lookup(iireq->mx->session->iimages, iireq->url)) {
+ /* inline image found in the cache, so we just ignore this reply */
+ goto done;
+ }
+
+ /* make a copy of the data */
+ ii_data = g_malloc(len);
+ memcpy(ii_data, (const char*) url_text, len);
+
+ /* we now have the inline image, store it in the imagestore */
+ id = purple_imgstore_add_with_id(ii_data, len, NULL);
+
+ /* map the inline image id to purple image id */
+ intptr = g_malloc(sizeof(int));
+ *intptr = id;
+ g_hash_table_insert(iireq->mx->session->iimages, iireq->url, intptr);
+
+ iireq->mx->flags |= PURPLE_MESSAGE_IMAGES;
+
+done:
+ iireq->mx->img_count--;
+ if ((iireq->mx->img_count == 0) && (iireq->mx->converted)) {
+ /*
+ * this was the last outstanding emoticon for this message,
+ * so we can now display it to the user.
+ */
+ mxit_show_message(iireq->mx);
+ }
+
+ g_free(iireq);
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the command identifier of this MXit Command.
+ *
+ * @param cmd The MXit command <key,value> map
+ * @return The MXit command identifier
+ */
+static MXitCommandType command_type(GHashTable* hash)
+{
+ char* op;
+ char* type;
+
+ op = g_hash_table_lookup(hash, "op");
+ if (op) {
+ if ( strcmp(op, "cmd") == 0 ) {
+ type = g_hash_table_lookup(hash, "type");
+ if (type == NULL) /* no command provided */
+ return MXIT_CMD_UNKNOWN;
+ else if (strcmp(type, "clrmsgscreen") == 0) /* clear the screen */
+ return MXIT_CMD_CLRSCR;
+ else if (strcmp(type, "sendsms") == 0) /* send an SMS */
+ return MXIT_CMD_SENDSMS;
+ else if (strcmp(type, "reply") == 0) /* list of options */
+ return MXIT_CMD_REPLY;
+ else if (strcmp(type, "platreq") == 0) /* platform request */
+ return MXIT_CMD_PLATREQ;
+ else if (strcmp(type, "selc") == 0) /* select contact */
+ return MXIT_CMD_SELECTCONTACT;
+ }
+ else if (strcmp(op, "img") == 0)
+ return MXIT_CMD_IMAGE;
+ }
+
+ return MXIT_CMD_UNKNOWN;
+}
+
+
+/*------------------------------------------------------------------------
+ * Tokenize a MXit Command string into a <key,value> map.
+ *
+ * @param cmd The MXit command string
+ * @return The <key,value> hash-map, or NULL on error.
+ */
+static GHashTable* command_tokenize(char* cmd)
+{
+ GHashTable* hash = NULL;
+ gchar** parts;
+ gchar* part;
+ int i = 0;
+
+#ifdef MXIT_DEBUG_COMMANDS
+ purple_debug_info(MXIT_PLUGIN_ID, "command: '%s'\n", cmd);
+#endif
+
+ /* explode the command into parts */
+ parts = g_strsplit(cmd, "|", 0);
+
+ hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+ /* now break part into a key & value */
+ while ((part = parts[i]) != NULL) {
+ char* value;
+
+ value = strchr(parts[i], '='); /* find start of value */
+ if (value != NULL) {
+ *value = '\0';
+ value++;
+ }
+
+#ifdef MXIT_DEBUG_COMMANDS
+ purple_debug_info(MXIT_PLUGIN_ID, " key='%s' value='%s'\n", parts[i], value);
+#endif
+
+ g_hash_table_insert(hash, g_strdup(parts[i]), g_strdup(value));
+
+ i++;
+ }
+
+ g_strfreev(parts);
+
+ return hash;
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a ClearScreen MXit command.
+ *
+ * @param session The MXit session object
+ * @param from The sender of the message.
+ */
+static void command_clearscreen(struct MXitSession* session, const char* from)
+{
+ PurpleConversation *conv;
+
+ conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, from, session->acc);
+ if (conv == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Conversation with '%s' not found\n", from);
+ return;
+ }
+
+ purple_conversation_clear_message_history(conv); // TODO: This doesn't actually clear the screen.
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a Reply MXit command.
+ *
+ * @param mx The received message data object
+ * @param hash The MXit command <key,value> map
+ */
+static void command_reply(struct RXMsgData* mx, GHashTable* hash)
+{
+ char* replymsg;
+ char* selmsg;
+
+ selmsg = g_hash_table_lookup(hash, "selmsg"); /* find the selection message */
+ replymsg = g_hash_table_lookup(hash, "replymsg"); /* find the reply message */
+ if ((selmsg) && (replymsg)) {
+ gchar* seltext = g_markup_escape_text(purple_url_decode(selmsg), -1);
+ gchar* replytext = g_markup_escape_text(purple_url_decode(replymsg), -1);
+
+ mxit_add_html_link( mx, replytext, seltext );
+
+ g_free(seltext);
+ g_free(replytext);
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a PlatformRequest MXit command.
+ *
+ * @param hash The MXit command <key,value> map
+ * @param msg The message to display (as generated so far)
+ */
+static void command_platformreq(GHashTable* hash, GString* msg)
+{
+ gchar* text = NULL;
+ char* selmsg;
+ char* dest;
+
+ selmsg = g_hash_table_lookup(hash, "selmsg"); /* find the selection message */
+ if (selmsg) {
+ text = g_markup_escape_text(purple_url_decode(selmsg), -1);
+ }
+
+ dest = g_hash_table_lookup(hash, "dest"); /* find the destination */
+ if (dest) {
+ g_string_append_printf(msg, "<a href=\"%s\">%s</a>", purple_url_decode(dest), (text) ? text : "Download"); /* add link to display message */
+ }
+
+ if (text)
+ g_free(text);
+}
+
+
+/*------------------------------------------------------------------------
+ * Process an inline image MXit command.
+ *
+ * @param mx The received message data object
+ * @param hash The MXit command <key,value> map
+ * @param msg The message to display (as generated so far)
+ */
+static void command_image(struct RXMsgData* mx, GHashTable* hash, GString* msg)
+{
+ const char* img;
+ const char* reply;
+ guchar* rawimg;
+ char link[256];
+ gsize rawimglen;
+ int imgid;
+
+ img = g_hash_table_lookup(hash, "dat");
+ if (img) {
+ rawimg = purple_base64_decode(img, &rawimglen);
+ //purple_util_write_data_to_file_absolute("/tmp/mxitinline.png", (char*) rawimg, rawimglen);
+ imgid = purple_imgstore_add_with_id(rawimg, rawimglen, NULL);
+ g_snprintf(link, sizeof(link), "<img id=\"%i\">", imgid);
+ g_string_append_printf(msg, "%s", link);
+ mx->flags |= PURPLE_MESSAGE_IMAGES;
+ }
+ else {
+ img = g_hash_table_lookup(hash, "src");
+ if (img) {
+ struct ii_url_request* iireq;
+
+ iireq = g_new0(struct ii_url_request,1);
+ iireq->url = g_strdup(purple_url_decode(img));
+ iireq->mx = mx;
+
+ g_string_append_printf(msg, "%s%s>", MXIT_II_TAG, iireq->url);
+ mx->got_img = TRUE;
+
+ /* lets first see if we dont have the inline image already in cache */
+ if (g_hash_table_lookup(mx->session->iimages, iireq->url)) {
+ /* inline image found in the cache, so we do not have to request it from the web */
+ g_free(iireq);
+ }
+ else {
+ /* send the request for the inline image */
+ purple_debug_info(MXIT_PLUGIN_ID, "sending request for inline image '%s'\n", iireq->url);
+
+ /* request the image (reference: "libpurple/util.h") */
+ purple_util_fetch_url_request(iireq->url, TRUE, NULL, TRUE, NULL, FALSE, mxit_cb_ii_returned, iireq);
+ mx->img_count++;
+ }
+ }
+ }
+
+ /* if this is a clickable image, show a click link */
+ reply = g_hash_table_lookup(hash, "replymsg");
+ if (reply) {
+ g_string_append_printf(msg, "\n");
+ mxit_add_html_link(mx, reply, "click here");
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received MXit Command message.
+ *
+ * @param mx The received message data object
+ * @param message The message text
+ * @return The length of the command
+ */
+//void mxit_command_received(struct MXitSession* session, const char* from, char* message, time_t timestamp)
+int mxit_parse_command(struct RXMsgData* mx, char* message)
+{
+ GHashTable* hash = NULL;
+ char* start;
+ char* end;
+
+ /* ensure that this is really a command */
+ if ( ( message[0] != ':' ) || ( message[1] != ':' ) ) {
+ /* this is not a command */
+ return 0;
+ }
+
+ start = message + 2;
+ end = strstr(start, ":");
+ if (end) {
+ /* end of a command found */
+ *end = '\0'; /* terminate command string */
+
+ hash = command_tokenize(start); /* break into <key,value> pairs */
+ if (hash) {
+ MXitCommandType type = command_type(hash);
+
+ switch (type) {
+ case MXIT_CMD_CLRSCR :
+ command_clearscreen(mx->session, mx->from);
+ break;
+ case MXIT_CMD_REPLY :
+ command_reply(mx, hash);
+ break;
+ case MXIT_CMD_PLATREQ :
+ command_platformreq(hash, mx->msg);
+ break;
+ case MXIT_CMD_IMAGE :
+ command_image(mx, hash, mx->msg);
+ break;
+ default :
+ /* command unknown, or not currently supported */
+ break;
+ }
+ g_hash_table_destroy(hash);
+ }
+ *end = ':';
+
+ return end - message;
+ }
+ else {
+ return 0;
+ }
+}
diff --git a/libpurple/protocols/mxit/formcmds.h b/libpurple/protocols/mxit/formcmds.h
new file mode 100644
index 0000000000..ce04803b40
--- /dev/null
+++ b/libpurple/protocols/mxit/formcmds.h
@@ -0,0 +1,35 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit Forms & Commands --
+ *
+ * Andrew Victor <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
+ */
+
+#ifndef _MXIT_FORMCMDS_H_
+#define _MXIT_FORMCMDS_H_
+
+#include "mxit.h"
+
+
+int mxit_parse_command(struct RXMsgData* mx, char* message);
+
+
+#endif /* _MXIT_FORMCMDS_H_ */
diff --git a/libpurple/protocols/mxit/http.c b/libpurple/protocols/mxit/http.c
new file mode 100644
index 0000000000..875e9ec2e8
--- /dev/null
+++ b/libpurple/protocols/mxit/http.c
@@ -0,0 +1,331 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit client protocol implementation --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "purple.h"
+
+#include "mxit.h"
+#include "protocol.h"
+#include "http.h"
+
+
+/* HTTP constants */
+#define HTTP_11_200_OK "HTTP/1.1 200 OK\r\n"
+#define HTTP_11_100_CONT "HTTP/1.1 100 Continue\r\n"
+#define HTTP_11_SEPERATOR "\r\n\r\n"
+#define HTTP_CONTENT_LEN "Content-Length: "
+
+
+/* define to enable HTTP debugging */
+#define DEBUG_HTTP
+
+
+/*------------------------------------------------------------------------
+ * This will freeup the memory used by a HTTP request structure
+ *
+ * @param req The HTTP structure's resources should be freed up
+ */
+static void free_http_request( struct http_request* req )
+{
+ g_free( req->host );
+ g_free( req->data );
+ g_free( req );
+}
+
+
+/*------------------------------------------------------------------------
+ * Write the request to the HTTP server.
+ *
+ * @param fd The file descriptor
+ * @param pktdata The packet data
+ * @param pktlen The length of the packet data
+ * @return Return -1 on error, otherwise 0
+ */
+static int mxit_http_raw_write( int fd, const char* pktdata, int pktlen )
+{
+ int written;
+ int res;
+
+ written = 0;
+ while ( written < pktlen ) {
+ res = write( fd, &pktdata[written], pktlen - written );
+ if ( res <= 0 ) {
+ /* error on socket */
+ if ( errno == EAGAIN )
+ continue;
+
+ purple_debug_error( MXIT_PLUGIN_ID, "Error while writing packet to HTTP server (%i)\n", res );
+ return -1;
+ }
+ written += res;
+ }
+
+ return 0;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback when data is received from the HTTP server.
+ *
+ * @param user_data The MXit session object
+ * @param source The file-descriptor on which data was received
+ * @param cond Condition which caused the callback (PURPLE_INPUT_READ)
+ */
+static void mxit_cb_http_read( gpointer user_data, gint source, PurpleInputCondition cond )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+ char buf[256];
+ int buflen;
+ char* body;
+ int bodylen;
+ char* ch;
+ int len;
+ char* tmp;
+ int res;
+ char* next;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_http_read\n" );
+
+ if ( session->rx_state == RX_STATE_RLEN ) {
+ /* we are reading in the HTTP headers */
+
+ /* copy partial headers if we have any part saved */
+ memcpy( buf, session->rx_dbuf, session->rx_i );
+ buflen = session->rx_i;
+
+ /* read bytes from the socket */
+ len = read( session->fd, buf + buflen, sizeof( buf ) - buflen );
+ if ( len <= 0 ) {
+ /* connection has been terminated, or error occured */
+ goto done;
+ }
+
+//nextpacket:
+
+#ifdef DEBUG_HTTP
+ purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST READ 1: (%i)\n", len );
+ dump_bytes( session, buf + buflen, len );
+#endif
+
+ /* see if we have all the HTTP headers yet */
+ ch = strstr( buf, HTTP_11_SEPERATOR );
+ if ( !ch ) {
+ /* we need to wait for more input, so save what we have */
+ session->rx_i = buflen + len;
+ memcpy( session->rx_dbuf, buf, session->rx_i );
+ return;
+ }
+ buflen += len;
+
+ /* we have the header's end now skip over the http seperator to get the body offset */
+ ch += strlen( HTTP_11_SEPERATOR );
+ *(ch - 1) = '\0';
+ body = ch;
+
+ res = buflen - ( ch - buf );
+ if ( res > 0 ) {
+ /* we read more bytes than just the header so copy it over */
+ memcpy( session->rx_dbuf, ch, res );
+ session->rx_i = res;
+ }
+ else {
+ session->rx_i = 0;
+ }
+
+ /* test for a good response */
+ if ( ( strncmp( buf, HTTP_11_200_OK, strlen( HTTP_11_200_OK ) ) != 0 ) && ( strncmp( buf, HTTP_11_100_CONT, strlen( HTTP_11_100_CONT ) ) != 0 ) ) {
+ /* bad result */
+ purple_debug_error( MXIT_PLUGIN_ID, "HTTP error: %s\n", ch );
+ goto done;
+ }
+
+ /* find the content-length */
+ ch = (char*) purple_strcasestr( buf, HTTP_CONTENT_LEN );
+ if ( !ch ) {
+ /* bad request. it does not contain a content-length header */
+ purple_debug_error( MXIT_PLUGIN_ID, "HTTP reply received without content-length header (ignoring packet)\n" );
+ goto done;
+ }
+
+ /* parse the content-length */
+ ch += strlen( HTTP_CONTENT_LEN );
+ tmp = strchr( ch, '\r' );
+ if ( !tmp ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Received bad HTTP reply packet (ignoring packet)\n" );
+ goto done;
+ }
+ tmp = g_strndup( ch, tmp - ch );
+ bodylen = atoi( tmp );
+ g_free( tmp );
+ tmp = NULL;
+
+ if ( buflen > ( ( body - buf ) + bodylen ) ) {
+ /* we have a second packet here */
+ next = body + bodylen;
+ session->rx_res = 0;
+ }
+ else {
+ session->rx_res = bodylen - session->rx_i;
+ }
+
+ if ( session->rx_res == 0 ) {
+ /* we have read all the data */
+ session->rx_i = bodylen;
+ session->rx_state = RX_STATE_PROC;
+ }
+ else {
+ /* there is still some data outstanding */
+ session->rx_state = RX_STATE_DATA;
+ }
+ }
+ else if ( session->rx_state == RX_STATE_DATA ) {
+ /* we are reading the HTTP content (body) */
+
+ /* read bytes from the socket */
+ len = read( session->fd, &session->rx_dbuf[session->rx_i], session->rx_res );
+ if ( len <= 0 ) {
+ /* connection has been terminated, or error occured */
+ goto done;
+ }
+
+#ifdef DEBUG_HTTP
+ purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST READ 2: (%i)\n", len );
+ dump_bytes( session, &session->rx_dbuf[session->rx_i], len );
+#endif
+ session->rx_i += len;
+ session->rx_res -= len;
+
+ if ( session->rx_res == 0 ) {
+ /* ok, so now we have read in the whole packet */
+ session->rx_state = RX_STATE_PROC;
+ }
+ }
+
+ if ( session->rx_state == RX_STATE_PROC ) {
+ mxit_parse_packet( session );
+
+#if 0
+ if ( next ) {
+ /* there is another packet of which we read some data */
+
+ /* reset input */
+ session->rx_state = RX_STATE_RLEN;
+ session->rx_lbuf[0] = '\0';
+ session->rx_i = 0;
+ session->rx_res = 0;
+
+ /* move read data */
+ len = next - buf;
+ buflen = len;
+ memcpy( buf, next, len );
+ goto nextpacket;
+ }
+#endif
+
+ /* we are done */
+ goto done;
+ }
+
+ return;
+done:
+ close( session->fd );
+ purple_input_remove( session->http_handler );
+ session->http_handler = 0;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback invoked once the connection has been established to the HTTP server,
+ * or on connection failure.
+ *
+ * @param user_data The MXit session object
+ * @param source The file-descriptor associated with the connection
+ * @param error_message Message explaining why the connection failed
+ */
+static void mxit_cb_http_connect( gpointer user_data, gint source, const gchar* error_message )
+{
+ struct http_request* req = (struct http_request*) user_data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_http_connect\n" );
+
+ /* source is the file descriptor of the new connection */
+ if ( source < 0 ) {
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_http_connect failed: %s\n", error_message );
+ purple_connection_error( req->session->con, _( "Unable to connect to the mxit HTTP server. Please check your server server settings." ) );
+ return;
+ }
+
+ /* we now have an open and active TCP connection to the mxit server */
+ req->session->fd = source;
+
+ /* reset the receive buffer */
+ req->session->rx_state = RX_STATE_RLEN;
+ req->session->rx_lbuf[0] = '\0';
+ req->session->rx_i = 0;
+ req->session->rx_res = 0;
+
+ /* start listening on the open connection for messages from the server (reference: "libpurple/eventloop.h") */
+ req->session->http_handler = purple_input_add( req->session->fd, PURPLE_INPUT_READ, mxit_cb_http_read, req->session );
+
+ /* actually send the request to the HTTP server */
+ mxit_http_raw_write( req->session->fd, req->data, req->datalen );
+
+ /* free up resources */
+ free_http_request( req );
+ req = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Create HTTP connection for sending a HTTP request
+ *
+ * @param session The MXit session object
+ * @param host The server name to connect to
+ * @param port The port number to connect to
+ * @param data The HTTP request data (including HTTP headers etc.)
+ * @param datalen The HTTP request data length
+ */
+void mxit_http_send_request( struct MXitSession* session, char* host, int port, const char* data, int datalen )
+{
+ PurpleProxyConnectData* con = NULL;
+ struct http_request* req;
+
+ /* build the http request */
+ req = g_new0( struct http_request, 1 );
+ req->session = session;
+ req->host = host;
+ req->port = port;
+ req->data = g_malloc0( datalen );
+ memcpy( req->data, data, datalen );
+ req->datalen = datalen;
+
+ /* open connection to the HTTP server */
+ con = purple_proxy_connect( NULL, session->acc, host, port, mxit_cb_http_connect, req );
+}
+
diff --git a/libpurple/protocols/mxit/http.h b/libpurple/protocols/mxit/http.h
new file mode 100644
index 0000000000..03808fff58
--- /dev/null
+++ b/libpurple/protocols/mxit/http.h
@@ -0,0 +1,47 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit client protocol implementation --
+ *
+ * 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
+ */
+
+
+#ifndef _MXIT_HTTP_H_
+#define _MXIT_HTTP_H_
+
+
+
+struct http_request
+{
+ struct MXitSession* session;
+ char* host;
+ int port;
+ char* data;
+ int datalen;
+};
+
+
+void mxit_http_send_request( struct MXitSession* session, char* host, int port, const char* data, int datalen );
+
+
+
+#endif /* _MXIT_HTTP_H_ */
+
diff --git a/libpurple/protocols/mxit/login.c b/libpurple/protocols/mxit/login.c
new file mode 100644
index 0000000000..1e7304df9d
--- /dev/null
+++ b/libpurple/protocols/mxit/login.c
@@ -0,0 +1,789 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit user login functionality --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "purple.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "cipher.h"
+#include "login.h"
+#include "profile.h"
+
+/* requesting captcha size */
+#define MXIT_CAPTCHA_HEIGHT 50
+#define MXIT_CAPTCHA_WIDTH 150
+
+
+/* prototypes */
+static void mxit_register_view( struct MXitSession* session );
+static void get_clientinfo( struct MXitSession* session );
+
+
+/*------------------------------------------------------------------------
+ * Create a new mxit session object
+ *
+ * @return The MXit session object
+ */
+static struct MXitSession* mxit_create_object( PurpleAccount* account )
+{
+ struct MXitSession* session = NULL;
+ PurpleConnection* con = NULL;
+
+ /* currently the wapsite does not handle a '+' in front of the username (mxitid) so we just strip it */
+ if ( account->username[0] == '+' ) {
+ char* fixed;
+
+ /* cut off the '+' */
+ fixed = g_strdup( &account->username[1] );
+ purple_account_set_username( account, fixed );
+ g_free( fixed );
+ }
+
+ session = g_new0( struct MXitSession, 1 );
+
+ /* configure the connection (reference: "libpurple/connection.h") */
+ con = purple_account_get_connection( account );
+ con->proto_data = session;
+ con->flags |= PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_HTML;
+ session->con = con;
+
+ /* add account */
+ session->acc = account;
+
+ /* configure the session (reference: "libpurple/account.h") */
+ g_strlcpy( session->server, purple_account_get_string( account, MXIT_CONFIG_SERVER_ADDR, DEFAULT_SERVER ), sizeof( session->server ) );
+ g_strlcpy( session->http_server, purple_account_get_string( account, MXIT_CONFIG_HTTPSERVER, DEFAULT_HTTP_SERVER ), sizeof( session->http_server ) );
+ session->port = purple_account_get_int( account, MXIT_CONFIG_SERVER_PORT, DEFAULT_PORT );
+ g_strlcpy( session->distcode, purple_account_get_string( account, MXIT_CONFIG_DISTCODE, "" ), sizeof( session->distcode ) );
+ g_strlcpy( session->clientkey, purple_account_get_string( account, MXIT_CONFIG_CLIENTKEY, "" ), sizeof( session->clientkey ) );
+ g_strlcpy( session->dialcode, purple_account_get_string( account, MXIT_CONFIG_DIALCODE, "" ), sizeof( session->dialcode ) );
+ session->http = purple_account_get_bool( account, MXIT_CONFIG_USE_HTTP, FALSE );
+ session->iimages = g_hash_table_new( g_str_hash, g_str_equal );
+ session->rx_state = RX_STATE_RLEN;
+ session->http_interval = MXIT_HTTP_POLL_MIN;
+ session->http_last_poll = time( NULL );
+
+ return session;
+}
+
+
+/*------------------------------------------------------------------------
+ * We now have a connection established with MXit, so we can start the
+ * login procedure
+ *
+ * @param session The MXit session object
+ */
+static void mxit_connected( struct MXitSession* session )
+{
+ int state;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_connected\n" );
+
+ session->flags |= MXIT_FLAG_CONNECTED;
+ purple_connection_update_progress( session->con, _( "Logging In..." ), 2, 4 );
+
+ /* create a timer to send a ping packet if the connection is idle */
+ session->last_tx = time( NULL );
+
+ /* encrypt the user password */
+ session->encpwd = mxit_encrypt_password( session );
+
+ state = purple_account_get_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
+ if ( state == MXIT_STATE_LOGIN ) {
+ /* create and send login packet */
+ mxit_send_login( session );
+ }
+ else {
+ if ( !session->profile ) {
+ /* we have lost the session profile, so ask the user to enter it again */
+ mxit_register_view( session );
+ }
+ else {
+ /* create and send the register packet */
+ mxit_send_register( session );
+ }
+ }
+
+ /* enable signals */
+ mxit_enable_signals( session );
+
+#ifdef MXIT_LINK_CLICK
+ /* register for uri click notification */
+ mxit_register_uri_handler();
+#endif
+
+ /* start the polling if this is a HTTP connection */
+ if ( session->http ) {
+ session->http_timer_id = purple_timeout_add_seconds( 2, mxit_manage_polling, session );
+ }
+
+ /* start the tx queue manager timer */
+ session->q_timer = purple_timeout_add_seconds( 2, mxit_manage_queue, session );
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback invoked once the connection has been established to the MXit server,
+ * or on connection failure.
+ *
+ * @param user_data The MXit session object
+ * @param source The file-descriptor associated with the connection
+ * @param error_message Message explaining why the connection failed
+ */
+static void mxit_cb_connect( gpointer user_data, gint source, const gchar* error_message )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_connect\n" );
+
+ /* source is the file descriptor of the new connection */
+ if ( source < 0 ) {
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_connect failed: %s\n", error_message );
+ purple_connection_error( session->con, _( "Unable to connect to the mxit server. Please check your server server settings." ) );
+ return;
+ }
+
+ /* we now have an open and active TCP connection to the mxit server */
+ session->fd = source;
+
+ /* start listening on the open connection for messages from the server (reference: "libpurple/eventloop.h") */
+ session->con->inpa = purple_input_add( session->fd, PURPLE_INPUT_READ, mxit_cb_rx, session );
+
+ mxit_connected( session );
+}
+
+
+/*------------------------------------------------------------------------
+ * Attempt to establish a connection to the MXit server.
+ *
+ * @param session The MXit session object
+ */
+static void mxit_login_connect( struct MXitSession* session )
+{
+ PurpleProxyConnectData* data = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_login_connect\n" );
+
+ purple_connection_update_progress( session->con, _( "Connecting..." ), 1, 4 );
+
+ /*
+ * at this stage we have all the user's information we require
+ * for logging into MXit. we will now create a new connection to
+ * a MXit server.
+ */
+
+ if ( !session->http ) {
+ /* socket connection */
+ data = purple_proxy_connect( session->con, session->acc, session->server, session->port, mxit_cb_connect, session );
+ if ( !data ) {
+ purple_connection_error( session->con, _( "Unable to connect to the mxit server. Please check your server server settings." ) );
+ return;
+ }
+ }
+ else {
+ /* http connection */
+ mxit_connected( session );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Register a new account with MXit
+ *
+ * @param gc The connection object
+ * @param fields This is the fields filled-in by the user
+ */
+static void mxit_cb_register_ok( PurpleConnection *gc, PurpleRequestFields *fields )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ struct MXitProfile* profile = session->profile;
+ const char* str;
+ const char* pin;
+ char* err = NULL;
+ int len;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_register_ok\n" );
+
+ if ( !PURPLE_CONNECTION_IS_VALID( gc ) ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Unable to register; account offline.\n" );
+ return;
+ }
+
+ /* nickname */
+ str = purple_request_fields_get_string( fields, "nickname" );
+ if ( ( !str ) || ( strlen( str ) < 3 ) ) {
+ err = "The nick name you entered is invalid.";
+ goto out;
+ }
+ g_strlcpy( profile->nickname, str, sizeof( profile->nickname ) );
+
+ /* birthdate */
+ str = purple_request_fields_get_string( fields, "bday" );
+ if ( ( !str ) || ( strlen( str ) < 10 ) || ( !validateDate( str ) ) ) {
+ err = "The birthday you entered is invalid. The correct format is: 'YYYY-MM-DD'.";
+ goto out;
+ }
+ g_strlcpy( profile->birthday, str, sizeof( profile->birthday ) );
+
+ /* gender */
+ if ( purple_request_fields_get_choice( fields, "male" ) == 0 )
+ profile->male = FALSE;
+ else
+ profile->male = TRUE;
+
+ /* pin */
+ pin = purple_request_fields_get_string( fields, "pin" );
+ if ( !pin ) {
+ err = "The PIN you entered is invalid.";
+ goto out;
+ }
+ len = strlen( pin );
+ if ( ( len < 7 ) || ( len > 10 ) ) {
+ err = "The PIN you entered has an invalid length [7-10].";
+ goto out;
+ }
+ for ( i = 0; i < len; i++ ) {
+ if ( !g_ascii_isdigit( pin[i] ) ) {
+ err = "The PIN is invalid. It should only consist of digits [0-9].";
+ goto out;
+ }
+ }
+ str = purple_request_fields_get_string( fields, "pin2" );
+ if ( ( !str ) || ( strcmp( pin, str ) != 0 ) ) {
+ err = "The two PINs you entered does not match.";
+ goto out;
+ }
+ g_strlcpy( profile->pin, pin, sizeof( profile->pin ) );
+
+out:
+ if ( !err ) {
+ purple_account_set_password( session->acc, session->profile->pin );
+ mxit_login_connect( session );
+ }
+ else {
+ /* show error to user */
+ mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Registration Error" ), _( err ) );
+ mxit_register_view( session );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Register a new account with MXit
+ *
+ * @param gc The connection object
+ * @param fields This is the fields filled-in by the user
+ */
+static void mxit_cb_register_cancel( PurpleConnection *gc, PurpleRequestFields *fields )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_register_cancel\n" );
+
+ /* disconnect */
+ purple_account_disconnect( gc->account );
+}
+
+
+/*------------------------------------------------------------------------
+ * Show a window to the user so that he can enter his information
+ *
+ * @param session The MXit session object
+ */
+static void mxit_register_view( struct MXitSession* session )
+{
+ struct MXitProfile* profile;
+ PurpleRequestFields* fields;
+ PurpleRequestFieldGroup* group;
+ PurpleRequestField* field;
+
+ if ( !session->profile ) {
+ /* we need to create a profile object here */
+ session->profile = g_new0( struct MXitProfile, 1 );
+ }
+ profile = session->profile;
+
+ fields = purple_request_fields_new();
+ group = purple_request_field_group_new( NULL );
+ purple_request_fields_add_group( fields, group );
+
+ /* mxit login name */
+ field = purple_request_field_string_new( "loginname", _( "MXit Login Name" ), purple_account_get_username( session->acc ), FALSE );
+ purple_request_field_string_set_editable( field, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* nick name */
+ field = purple_request_field_string_new( "nickname", _( "Nick Name" ), profile->nickname, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* birthday */
+ field = purple_request_field_string_new( "bday", _( "Birthday" ), profile->birthday, FALSE );
+ purple_request_field_string_set_default_value( field, "YYYY-MM-DD" );
+ purple_request_field_group_add_field( group, field );
+
+ /* gender */
+ field = purple_request_field_choice_new( "male", _( "Gender" ), ( profile->male ) ? 1 : 0 );
+ purple_request_field_choice_add( field, _( "Female" ) ); /* 0 */
+ purple_request_field_choice_add( field, _( "Male" ) ); /* 1 */
+ purple_request_field_group_add_field( group, field );
+
+ /* pin */
+ field = purple_request_field_string_new( "pin", _( "PIN" ), profile->pin, FALSE );
+ purple_request_field_string_set_masked( field, TRUE );
+ purple_request_field_group_add_field( group, field );
+ field = purple_request_field_string_new( "pin2", _( "Verify PIN" ), "", FALSE );
+ purple_request_field_string_set_masked( field, TRUE );
+ purple_request_field_group_add_field( group, field );
+
+ /* show the form to the user to complete */
+ purple_request_fields( session->con, _( "Register New MXit Account" ), _( "Register New MXit Account" ), _( "Please fill in the following fields:" ), fields, _( "OK" ), G_CALLBACK( mxit_cb_register_ok ), _( "Cancel" ), G_CALLBACK( mxit_cb_register_cancel ), session->acc, NULL, NULL, session->con );
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback function invoked once the Authorization information has been submitted
+ * to the MXit WAP site.
+ *
+ * @param url_data libPurple internal object (see purple_util_fetch_url_request)
+ * @param user_data The MXit session 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 mxit_cb_clientinfo2( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+ gchar** parts;
+ gchar** host;
+ int state;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_clientinfo_cb2\n" );
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "HTTP RESPONSE: '%s'\n", url_text );
+#endif
+
+ if ( !url_text ) {
+ /* no reply from the WAP site */
+ purple_connection_error( session->con, _( "Error contacting the MXit WAP site. Please try again later." ) );
+ return;
+ }
+
+ /* explode the response from the WAP site into an array */
+ parts = g_strsplit( url_text, ";", 15 );
+
+ if ( !parts ) {
+ /* wapserver error */
+ purple_connection_error( session->con, _( "MXit is currently unable to process the request. Please try again later." ) );
+ return;
+ }
+
+ /* check wapsite return code */
+ switch ( parts[0][0] ) {
+ case '0' :
+ /* valid reply! */
+ break;
+ case '1' :
+ purple_connection_error( session->con, _( "Wrong security code entered. Please try again later." ) );
+ return;
+ case '2' :
+ purple_connection_error( session->con, _( "Your session has expired. Please try again later." ) );
+ return;
+ case '5' :
+ purple_connection_error( session->con, _( "Invalid country selected. Please try again." ) );
+ return;
+ case '6' :
+ purple_connection_error( session->con, _( "Username is not registered. Please register first." ) );
+ return;
+ case '7' :
+ purple_connection_error( session->con, _( "Username is already registered. Please choose another username." ) );
+ /* this user's account already exists, so we need to change the registration login flag to be login */
+ purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
+ return;
+ case '3' :
+ case '4' :
+ default :
+ purple_connection_error( session->con, _( "Internal error. Please try again later." ) );
+ return;
+ }
+
+ /* now parse and split the distribution code and the client key */
+ g_strlcpy( session->distcode, &parts[1][2], 36 + 1 );
+ g_strlcpy( session->clientkey, &parts[1][38], 8 + 1 );
+
+ /* get the dial code for the client */
+ g_strlcpy( session->dialcode, parts[4], sizeof( session->dialcode ) );
+
+ /* parse the proxy server address and port number */
+ host = g_strsplit( parts[2], ":", 4 );
+ g_strlcpy( session->server, &host[1][2], sizeof( session->server ) );
+ session->port = atoi( &host[2][0] );
+
+ /* parse the http proxy server address and port number */
+ g_strlcpy( session->http_server, parts[3], sizeof( session->http_server ) );
+
+ purple_debug_info( MXIT_PLUGIN_ID, "distcode='%s', clientkey='%s', dialcode='%s'\n", session->distcode, session->clientkey, session->dialcode );
+ purple_debug_info( MXIT_PLUGIN_ID, "sock_server='%s', http_server='%s', port='%i', cc='%s'\n", session->server, session->http_server, session->port, parts[11] );
+
+ /* save the information (reference: "libpurple/account.h") */
+ purple_account_set_string( session->acc, MXIT_CONFIG_DISTCODE, session->distcode );
+ purple_account_set_string( session->acc, MXIT_CONFIG_CLIENTKEY, session->clientkey );
+ purple_account_set_string( session->acc, MXIT_CONFIG_DIALCODE, session->dialcode );
+ purple_account_set_string( session->acc, MXIT_CONFIG_SERVER_ADDR, session->server );
+ purple_account_set_int( session->acc, MXIT_CONFIG_SERVER_PORT, session->port );
+ purple_account_set_string( session->acc, MXIT_CONFIG_HTTPSERVER, session->http_server );
+
+ /* update the state */
+ state = purple_account_get_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
+ if ( state == MXIT_STATE_REGISTER1 )
+ purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_REGISTER2 );
+
+ /* freeup the memory */
+ g_strfreev( host );
+ g_strfreev( parts );
+
+ if ( state == MXIT_STATE_LOGIN ) {
+ /* now we can continue with the login process */
+ mxit_login_connect( session );
+ }
+ else {
+ /* the user is registering so we need to get more information from him/her first to complete the process */
+ mxit_register_view( session );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Free up the data associated with the Authorization process.
+ *
+ * @param data The data object to free
+ */
+static void free_logindata( struct login_data* data )
+{
+ if ( !data )
+ return;
+
+ /* free up the login resources */
+ g_free( data->wapserver );
+ g_free( data->sessionid );
+ g_free( data->captcha );
+ g_free( data->cc );
+ g_free( data->locale );
+ g_free( data );
+}
+
+
+/*------------------------------------------------------------------------
+ * This function is called when the user accepts the Authorization form.
+ *
+ * @param gc The connection object
+ * @param fields The list of fields in the accepted form
+ */
+static void mxit_cb_captcha_ok( PurpleConnection* gc, PurpleRequestFields* fields )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ PurpleUtilFetchUrlData* url_data;
+ PurpleRequestField* field;
+ const char* captcha_resp;
+ GList* entries;
+ GList* entry;
+ char* url;
+ int state;
+
+ /* get the captcha response */
+ captcha_resp = purple_request_fields_get_string( fields, "code" );
+ if ( ( captcha_resp == NULL ) || ( captcha_resp[0] == '\0' ) ) {
+ /* the user did not fill in the captcha */
+ mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Error" ), _( "You did not enter the security code" ) );
+ free_logindata( session->logindata );
+ purple_account_disconnect( session->acc );
+ return;
+ }
+
+ /* get chosen country */
+ field = purple_request_fields_get_field( fields, "country" );
+ entries = purple_request_field_list_get_selected( field );
+ entry = g_list_first( entries );
+ session->logindata->cc = purple_request_field_list_get_data( field, entry->data );
+ purple_account_set_string( session->acc, MXIT_CONFIG_COUNTRYCODE, session->logindata->cc );
+
+ /* get chosen language */
+ field = purple_request_fields_get_field( fields, "locale" );
+ entries = purple_request_field_list_get_selected( field );
+ entry = g_list_first( entries );
+ session->logindata->locale = purple_request_field_list_get_data( field, entry->data );
+ purple_account_set_string( session->acc, MXIT_CONFIG_LOCALE, session->logindata->locale );
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "cc='%s', locale='%s', captcha='%s'\n", session->logindata->cc, session->logindata->locale, captcha_resp );
+#endif
+
+ /* get state */
+ state = purple_account_get_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
+
+ url = g_strdup_printf( "%s?type=getpid&sessionid=%s&login=%s&ver=%s&clientid=%s&cat=%s&chalresp=%s&cc=%s&loc=%s&path=%i&brand=%s&model=%s&h=%i&w=%i&ts=%li",
+ session->logindata->wapserver, session->logindata->sessionid, purple_url_encode( session->acc->username ), MXIT_CP_RELEASE, MXIT_CLIENT_ID, MXIT_CP_ARCH,
+ captcha_resp, session->logindata->cc, session->logindata->locale, ( state == MXIT_STATE_REGISTER1 ) ? 0 : 1, MXIT_CP_PLATFORM, MXIT_CP_OS,
+ MXIT_CAPTCHA_HEIGHT, MXIT_CAPTCHA_WIDTH, time( NULL ) );
+ url_data = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_clientinfo2, session );
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "HTTP REQUEST: '%s'\n", url );
+#endif
+ g_free( url );
+
+ /* free up the login resources */
+ free_logindata( session->logindata );
+}
+
+
+/*------------------------------------------------------------------------
+ * This function is called when the user cancels the Authorization form.
+ *
+ * @param gc The connection object
+ * @param fields The list of fields in the cancelled form
+ */
+static void mxit_cb_captcha_cancel( PurpleConnection* gc, PurpleRequestFields* fields )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+
+ /* free up the login resources */
+ free_logindata( session->logindata );
+
+ /* we cannot continue, so we disconnect this account */
+ purple_account_disconnect( session->acc );
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback function invoked once the client information has been retrieved from
+ * the MXit WAP site. Display page where user can select their authorization information.
+ *
+ * @param url_data libPurple internal object (see purple_util_fetch_url_request)
+ * @param user_data The MXit session 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 mxit_cb_clientinfo1( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+ struct login_data* logindata;
+ PurpleRequestFields* fields;
+ PurpleRequestFieldGroup* group = NULL;
+ PurpleRequestField* field = NULL;
+ gchar** parts;
+ gchar** countries;
+ gchar** locales;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_clientinfo_cb1\n" );
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "RESPONSE: %s\n", url_text );
+#endif
+
+ if ( !url_text ) {
+ /* no reply from the WAP site */
+ purple_connection_error( session->con, _( "Error contacting the MXit WAP site. Please try again later." ) );
+ return;
+ }
+
+ /* explode the response from the WAP site into an array */
+ parts = g_strsplit( url_text, ";", 15 );
+
+ if ( ( !parts ) || ( parts[0][0] != '0' ) ) {
+ /* server could not find the user */
+ purple_connection_error( session->con, _( "MXit is currently unable to process the request. Please try again later." ) );
+ return;
+ }
+
+ /* save received settings */
+ logindata = g_new0( struct login_data, 1 );
+ logindata->wapserver = g_strdup( parts[1] );
+ logindata->sessionid = g_strdup( parts[2] );
+ session->logindata = logindata;
+
+ /* now generate the popup requesting the user for action */
+
+ fields = purple_request_fields_new();
+ group = purple_request_field_group_new( NULL );
+ purple_request_fields_add_group( fields, group );
+
+ /* add the captcha */
+ logindata->captcha = purple_base64_decode( parts[3], &logindata->captcha_size );
+ field = purple_request_field_image_new( "capcha", _( "Security Code" ), (gchar*) logindata->captcha, logindata->captcha_size );
+ purple_request_field_group_add_field( group, field );
+
+ /* ask for input */
+ field = purple_request_field_string_new( "code", _( "Enter Security Code" ), NULL, FALSE );
+ purple_request_field_group_add_field( group, field );
+
+ /* choose your country, but be careful, we already know your IP! ;-) */
+ countries = g_strsplit( parts[4], ",", 500 );
+ field = purple_request_field_list_new( "country", _( "Your Country" ) );
+ purple_request_field_list_set_multi_select( field, FALSE );
+ for ( i = 0; countries[i]; i++ ) {
+ gchar** country;
+
+ country = g_strsplit( countries[i], "|", 2 );
+ if ( !country ) {
+ /* oops, this is not good, time to bail */
+ break;
+ }
+ purple_request_field_list_add( field, country[1], g_strdup( country[0] ) );
+ if ( strcmp( country[1], parts[6] ) == 0 ) {
+ /* based on the user's ip, this is his current country code, so we default to it */
+ purple_request_field_list_add_selected( field, country[1] );
+ }
+ g_strfreev( country );
+ }
+ purple_request_field_group_add_field( group, field );
+
+ /* choose your language */
+ locales = g_strsplit( parts[5], ",", 200 );
+ field = purple_request_field_list_new( "locale", _( "Your Language" ) );
+ purple_request_field_list_set_multi_select( field, FALSE );
+ for ( i = 0; locales[i]; i++ ) {
+ gchar** locale;
+
+ locale = g_strsplit( locales[i], "|", 2 );
+ if ( !locale ) {
+ /* oops, this is not good, time to bail */
+ break;
+ }
+ purple_request_field_list_add( field, locale[1], g_strdup( locale[0] ) );
+ g_strfreev( locale );
+ }
+ purple_request_field_list_add_selected( field, "English" );
+ purple_request_field_group_add_field( group, field );
+
+ /* display the form to the user and wait for his/her input */
+ purple_request_fields( session->con, "MXit", _( "MXit Authorization" ), _( "MXit account validation" ), fields,
+ _( "Continue" ), G_CALLBACK( mxit_cb_captcha_ok ), _( "Cancel" ), G_CALLBACK( mxit_cb_captcha_cancel ), session->acc, NULL, NULL, session->con );
+
+ /* freeup the memory */
+ g_strfreev( parts );
+}
+
+
+/*------------------------------------------------------------------------
+ * Initiate a request for the client information (distribution code, client key, etc)
+ * required for logging in from the MXit WAP site.
+ *
+ * @param session The MXit session object
+ */
+static void get_clientinfo( struct MXitSession* session )
+{
+ PurpleUtilFetchUrlData* url_data;
+ const char* wapserver;
+ char* url;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "get_clientinfo\n" );
+
+ purple_connection_update_progress( session->con, _( "Retrieving User Information..." ), 0, 4 );
+
+ /* get the WAP site as was configured by the user in the advanced settings */
+ wapserver = purple_account_get_string( session->acc, MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE );
+
+ /* reference: "libpurple/util.h" */
+ url = g_strdup_printf( "%s/res/?type=challenge&getcountries=true&getlanguage=true&getimage=true&h=%i&w=%i&ts=%li", wapserver, MXIT_CAPTCHA_HEIGHT, MXIT_CAPTCHA_WIDTH, time( NULL ) );
+ url_data = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_clientinfo1, session );
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "HTTP REQUEST: '%s'\n", url );
+#endif
+ g_free( url );
+}
+
+
+/*------------------------------------------------------------------------
+ * Log the user into MXit.
+ *
+ * @param account The account object
+ */
+void mxit_login( PurpleAccount* account )
+{
+ struct MXitSession* session = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_login\n" );
+
+ /* create and save a new mxit session */
+ session = mxit_create_object( account );
+
+ /*
+ * before we can login we need to have a valid distribution code and client key for authentication.
+ * if we don't have any info saved from a previous login, we need to get it from the MXit WAP site.
+ * we do cache it, so this step is only done on the very first login for each account.
+ */
+ if ( ( session->distcode == NULL ) || ( strlen( session->distcode ) == 0 ) ) {
+ /* this must be the very first login, so we need to retrieve the user information */
+ get_clientinfo( session );
+ }
+ else {
+ /* we can continue with the login */
+ mxit_login_connect( session );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Perform a reconnect to the MXit server, and maintain same session object.
+ *
+ * @param account The account object
+ */
+void mxit_reconnect( struct MXitSession* session )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_reconnect\n" );
+
+ /* close existing connection */
+ session->flags &= ~MXIT_FLAG_CONNECTED;
+ purple_proxy_connect_cancel_with_handle( session->con );
+
+ /* perform the re-connect */
+ mxit_login_connect( session );
+}
+
+
+/*------------------------------------------------------------------------
+ * Register a new account with MXit
+ *
+ * @param acc The account object
+ */
+void mxit_register( PurpleAccount* account )
+{
+ struct MXitSession* session = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_register\n" );
+
+ /* create and save a new mxit session */
+ session = mxit_create_object( account );
+ purple_account_set_int( account, MXIT_CONFIG_STATE, MXIT_STATE_REGISTER1 );
+
+ get_clientinfo( session );
+}
+
diff --git a/libpurple/protocols/mxit/login.h b/libpurple/protocols/mxit/login.h
new file mode 100644
index 0000000000..14947b08a7
--- /dev/null
+++ b/libpurple/protocols/mxit/login.h
@@ -0,0 +1,45 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit user login functionality --
+ *
+ * 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
+ */
+
+#ifndef _MXIT_LOGIN_H_
+#define _MXIT_LOGIN_H_
+
+
+struct login_data {
+ char* wapserver; /* direct WAP server for postback */
+ char* sessionid; /* unique session id */
+ guchar* captcha; /* actual captcha (PNG) */
+ gsize captcha_size; /* captcha size */
+ char* cc; /* country code */
+ char* locale; /* locale (language) */
+};
+
+
+void mxit_login( PurpleAccount* account );
+void mxit_register( PurpleAccount* account );
+void mxit_reconnect( struct MXitSession* session );
+
+
+#endif /* _MXIT_LOGIN_H_ */
diff --git a/libpurple/protocols/mxit/markup.c b/libpurple/protocols/mxit/markup.c
new file mode 100644
index 0000000000..553bc64be2
--- /dev/null
+++ b/libpurple/protocols/mxit/markup.c
@@ -0,0 +1,1192 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- convert between MXit and libPurple markup --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "purple.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "markup.h"
+#include "chunk.h"
+#include "formcmds.h"
+#include "roster.h"
+
+
+/* define this to enable emoticon (markup) debugging */
+#undef MXIT_DEBUG_EMO
+/* define this to enable markup conversion debugging */
+#undef MXIT_DEBUG_MARKUP
+
+
+#define MXIT_FRAME_MAGIC "MXF\x01" /* mxit emoticon magic number */
+#define MXIT_MAX_EMO_ID 16 /* maximum emoticon ID length */
+#define COLORCODE_LEN 6 /* colour code ID length */
+
+
+/* HTML tag types */
+#define MXIT_TAG_COLOR 0x01 /* font color tag */
+#define MXIT_TAG_SIZE 0x02 /* font size tag */
+#define MXIT_MAX_MSG_TAGS 90 /* maximum tags per message (pigdin hack work around) */
+
+/*
+ * a HTML tag object
+ */
+struct tag {
+ char type;
+ char* value;
+};
+
+
+#define MXIT_VIBE_MSG_COLOR "#9933FF"
+
+/* vibes */
+static const char* vibes[] = {
+ /* 0 */ "Cool Vibrations",
+ /* 1 */ "Purple Rain",
+ /* 2 */ "Polite",
+ /* 3 */ "Rock n Roll",
+ /* 4 */ "Summer Slumber",
+ /* 5 */ "Electric Razor",
+ /* 6 */ "S.O.S",
+ /* 7 */ "Jack Hammer",
+ /* 8 */ "Bumble Bee",
+ /* 9 */ "Ripple"
+};
+
+
+
+#ifdef MXIT_DEBUG_EMO
+/*------------------------------------------------------------------------
+ * Dump a byte buffer as hexadecimal to the console for debugging purposes.
+ *
+ * @param buf The data to dump
+ * @param len The length of the data
+ */
+static void hex_dump( const char* buf, int len )
+{
+ char msg[256];
+ int pos;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Dumping data (%i bytes)\n", len );
+
+ memset( msg, 0x00, sizeof( msg ) );
+ pos = 0;
+
+ for ( i = 0; i < len; i++ ) {
+
+ if ( pos == 0 )
+ pos += sprintf( &msg[pos], "%04i: ", i );
+
+ pos += sprintf( &msg[pos], "0x%02X ", (unsigned char) buf[i] );
+
+ if ( i % 16 == 15 ) {
+ pos += sprintf( &msg[pos], "\n" );
+ purple_debug_info( MXIT_PLUGIN_ID, msg );
+ pos = 0;
+ }
+ else if ( i % 16 == 7 )
+ pos += sprintf( &msg[pos], " " );
+ }
+
+ if ( pos > 0 ) {
+ pos += sprintf( &msg[pos], "\n" );
+ purple_debug_info( MXIT_PLUGIN_ID, msg );
+ pos = 0;
+ }
+}
+#endif
+
+
+/*------------------------------------------------------------------------
+ * Adds a link to a message
+ *
+ * @param mx The Markup message object
+ * @param linkname This is the what will be returned when the link gets clicked
+ * @param displayname This is the name for the link which will be displayed in the UI
+ */
+void mxit_add_html_link( struct RXMsgData* mx, const char* linkname, const char* displayname )
+{
+#ifdef MXIT_LINK_CLICK
+ char retstr[256];
+ gchar* retstr64;
+ char link[256];
+ int len;
+
+ len = g_snprintf( retstr, sizeof( retstr ), "%s|%s|%s|%s|%s", MXIT_LINK_KEY, purple_account_get_username( mx->session->acc ),
+ purple_account_get_protocol_id( mx->session->acc ), mx->from, linkname );
+ retstr64 = purple_base64_encode( (const unsigned char*) retstr, len );
+ g_snprintf( link, sizeof( link ), "%s%s", MXIT_LINK_PREFIX, retstr64 );
+ g_free( retstr64 );
+
+ g_string_append_printf( mx->msg, "<a href=\"%s\">%s</a>", link, displayname );
+#else
+ g_string_append_printf( mx->msg, "<b>%s</b>", linkname );
+#endif
+}
+
+
+/*------------------------------------------------------------------------
+ * Extract an ASN.1 formatted length field from the data.
+ *
+ * @param data The source data
+ * @param size The extracted length
+ * @return The number of bytes extracted
+ */
+static unsigned int asn_getlength( const char* data, int* size )
+{
+ unsigned int len = 0;
+ unsigned char bytes;
+ unsigned char byte;
+ int i;
+
+ /* first byte specifies the number of bytes in the length */
+ bytes = ( data[0] & ~0x80 );
+ if ( bytes > sizeof( unsigned int ) ) {
+ /* file too big! */
+ return -1;
+ }
+ data++;
+
+ /* parse out the actual length */
+ for ( i = 0; i < bytes; i++ ) {
+ byte = data[i];
+ len <<= 8;
+ len += byte;
+ }
+
+ *size = len;
+ return bytes + 1;
+}
+
+
+/*------------------------------------------------------------------------
+ * Extract an ASN.1 formatted UTF-8 string field from the data.
+ *
+ * @param data The source data
+ * @param type Expected type of string
+ * @param utf8 The extracted string. Must be deallocated by caller.
+ * @return The number of bytes extracted
+ */
+static int asn_getUtf8( const char* data, char type, char** utf8 )
+{
+ int len;
+
+ /* validate the field type [1 byte] */
+ if ( data[0] != type ) {
+ /* this is not a utf-8 string! */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid UTF-8 encoded string in ASN data (0x%02X)\n", (unsigned char) data[0] );
+ return -1;
+ }
+
+ len = data[1]; /* length field [1 bytes] */
+ *utf8 = g_malloc( len + 1 );
+ memcpy( *utf8, &data[2], len ); /* data field */
+ (*utf8)[len] = '\0';
+
+ return ( len + 2 );
+}
+
+
+/*------------------------------------------------------------------------
+ * Free data associated with a Markup message object.
+ *
+ * @param mx The Markup message object
+ */
+static void free_markupdata( struct RXMsgData* mx )
+{
+ if ( mx ) {
+ if ( mx->msg )
+ g_string_free( mx->msg, TRUE );
+ if ( mx->from )
+ g_free( mx->from );
+ g_free( mx );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Split the message into smaller messages and send them one at a time
+ * to pidgin to be displayed on the UI
+ *
+ * @param mx The received message object
+ */
+static void mxit_show_split_message( struct RXMsgData* mx )
+{
+ const char* cont = "<font color=\"#999999\">continuing...</font>\n";
+ GString* msg = NULL;
+ char* ch = NULL;
+ int pos = 0;
+ int start = 0;
+ int l_nl = 0;
+ int l_sp = 0;
+ int l_gt = 0;
+ int stop = 0;
+ int tags = 0;
+ int segs = 0;
+ gboolean intag = FALSE;
+
+ /*
+ * awful hack to work around the awful hack in pidgin to work around GtkIMHtml's
+ * inefficient rendering of messages with lots of formatting changes.
+ * (reference: see the function pidgin_conv_write_conv() in gtkconv.c) the issue
+ * is that when you have more than 100 '<' characters in the message passed to
+ * pidgin, none of the markup (including links) are rendered and thus just dump
+ * all the text as is to the conversation window. this message dump is very
+ * confusing and makes it totally unusable. to work around this we will count
+ * the amount of tags and if its more than the pidgin threshold, we will just
+ * break the message up into smaller parts and send them seperately to pidgin.
+ * to the user it will look like multiple messages, but at least he will be able
+ * to use and understand it.
+ */
+
+ ch = mx->msg->str;
+ pos = start;
+ while ( ch[pos] ) {
+
+ if ( ch[pos] == '<' ) {
+ tags++;
+ intag = TRUE;
+ }
+ else if ( ch[pos] == '\n' ) {
+ l_nl = pos;
+ }
+ else if ( ch[pos] == '>' ) {
+ l_gt = pos;
+ intag = FALSE;
+ }
+ else if ( ch[pos] == ' ' ) {
+ /* ignore spaces inside tags */
+ if ( !intag )
+ l_sp = pos;
+ }
+ else if ( ( ch[pos] == 'w' ) && ( pos + 4 < mx->msg->len ) && ( memcmp( &ch[pos], "www.", 4 ) == 0 ) ) {
+ tags += 2;
+ }
+ else if ( ( ch[pos] == 'h' ) && ( pos + 8 < mx->msg->len ) && ( memcmp( &ch[pos], "http://", 7 ) == 0 ) ) {
+ tags += 2;
+ }
+
+ if ( tags > MXIT_MAX_MSG_TAGS ) {
+ /* we have reached the maximum amount of tags pidgin (gtk) can handle per message.
+ so its time to send what we have and then start building a new message */
+
+ /* now find the right place to break the message */
+ if ( l_nl > start ) {
+ /* break at last '\n' char */
+ stop = l_nl;
+ ch[stop] = '\0';
+ msg = g_string_new( &ch[start] );
+ ch[stop] = '\n';
+ }
+ else if ( l_sp > start ) {
+ /* break at last ' ' char */
+ stop = l_sp;
+ ch[stop] = '\0';
+ msg = g_string_new( &ch[start] );
+ ch[stop] = ' ';
+ }
+ else {
+ /* break at the last '>' char */
+ char t;
+ stop = l_gt + 1;
+ t = ch[stop];
+ ch[stop] = '\0';
+ msg = g_string_new( &ch[start] );
+ ch[stop] = t;
+ stop--;
+ }
+
+ /* build the string */
+ if ( segs )
+ g_string_prepend( msg, cont );
+
+ /* push message to pidgin */
+ serv_got_im( mx->session->con, mx->from, msg->str, mx->flags, mx->timestamp );
+ g_string_free( msg, TRUE );
+ msg = NULL;
+
+ tags = 0;
+ segs++;
+ start = stop + 1;
+ }
+
+ pos++;
+ }
+
+ if ( start != pos ) {
+ /* send the last part of the message */
+
+ /* build the string */
+ ch[pos] = '\0';
+ msg = g_string_new( &ch[start] );
+ ch[pos] = '\n';
+ if ( segs )
+ g_string_prepend( msg, cont );
+
+ /* push message to pidgin */
+ serv_got_im( mx->session->con, mx->from, msg->str, mx->flags, mx->timestamp );
+ g_string_free( msg, TRUE );
+ msg = NULL;
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Insert custom emoticons and inline images into the message (if there
+ * are any), then give the message to the UI to display to the user.
+ *
+ * @param mx The received message object
+ */
+void mxit_show_message( struct RXMsgData* mx )
+{
+ char* pos;
+ int start;
+ unsigned int end;
+ int emo_ofs;
+ char ii[128];
+ char tag[64];
+ int* img_id;
+
+ if ( mx->got_img ) {
+ /* search and replace all emoticon tags with proper image tags */
+
+ while ( ( pos = strstr( mx->msg->str, MXIT_II_TAG ) ) != NULL ) {
+ start = pos - mx->msg->str; /* offset at which MXIT_II_TAG starts */
+ emo_ofs = start + strlen( MXIT_II_TAG ); /* offset at which EMO's ID starts */
+ end = emo_ofs + 1; /* offset at which MXIT_II_TAG ends */
+
+ while ( ( end < mx->msg->len ) && ( mx->msg->str[end] != '>' ) )
+ end++;
+
+ if ( end == mx->msg->len ) /* end of emoticon tag not found */
+ break;
+
+ memset( ii, 0x00, sizeof( ii ) );
+ memcpy( ii, &mx->msg->str[emo_ofs], end - emo_ofs );
+
+ /* remove inline image tag */
+ g_string_erase( mx->msg, start, ( end - start ) + 1 );
+
+ /* find the image entry */
+ img_id = (int*) g_hash_table_lookup( mx->session->iimages, ii );
+ if ( !img_id ) {
+ /* inline image not found, so we will just skip it */
+ purple_debug_error( MXIT_PLUGIN_ID, "inline image NOT found (%s)\n", ii );
+ }
+ else {
+ /* insert img tag */
+ g_snprintf( tag, sizeof( tag ), "<img id=\"%i\">", *img_id );
+ g_string_insert( mx->msg, start, tag );
+ }
+ }
+ }
+
+#ifdef MXIT_DEBUG_MARKUP
+ purple_debug_info( MXIT_PLUGIN_ID, "Markup RX (converted): '%s'\n", mx->msg->str );
+#endif
+
+ if ( mx->processed ) {
+ /* this message has already been taken care of, so just ignore it here */
+ }
+ else if ( mx->chatid < 0 ) {
+ /* normal chat message */
+ //serv_got_im( mx->session->con, mx->from, mx->msg->str, mx->flags, mx->timestamp );
+ mxit_show_split_message( mx );
+ }
+ else {
+ /* this is a multimx message */
+ serv_got_chat_in( mx->session->con, mx->chatid, mx->from, mx->flags, mx->msg->str, mx->timestamp);
+ }
+
+ /* freeup resource */
+ free_markupdata( mx );
+}
+
+
+/*------------------------------------------------------------------------
+ * Extract the custom emoticon ID from the message.
+ *
+ * @param message The input data
+ * @param emid The extracted emoticon ID
+ */
+static void parse_emoticon_str( const char* message, char* emid )
+{
+ int i;
+
+ for ( i = 0; ( message[i] != '\0' && message[i] != '}' && i < MXIT_MAX_EMO_ID ); i++ ) {
+ emid[i] = message[i];
+ }
+
+ if ( message[i] == '\0' ) {
+ /* end of message reached, ignore the tag */
+ emid[0] = '\0';
+ }
+ else if ( i == MXIT_MAX_EMO_ID ) {
+ /* invalid tag length, ignore the tag */
+ emid[0] = '\0';
+ }
+ else
+ emid[i] = '\0';
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback function invoked when a custom emoticon request to the WAP site completes.
+ *
+ * @param url_data
+ * @param user_data The Markup message object
+ * @param url_text The data returned from the WAP site
+ * @param len The length of the data returned
+ * @param error_message Descriptive error message
+ */
+static void emoticon_returned( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message )
+{
+ struct RXMsgData* mx = (struct RXMsgData*) user_data;
+ const char* data = url_text;
+ unsigned int pos = 0;
+ char emo[16];
+ int id;
+ char* str;
+ int em_size = 0;
+ char* em_data = NULL;
+ char* em_id = NULL;
+ int* intptr = NULL;
+ int res;
+
+#ifdef MXIT_DEBUG_EMO
+ purple_debug_info( MXIT_PLUGIN_ID, "emoticon_returned\n" );
+#endif
+
+ if ( !url_text ) {
+ /* no reply from the WAP site */
+ purple_debug_error( MXIT_PLUGIN_ID, "Error contacting the MXit WAP site. Please try again later (emoticon).\n" );
+ goto done;
+ }
+
+#ifdef MXIT_DEBUG_EMO
+ hex_dump( data, len );
+#endif
+
+ /* parse out the emoticon */
+ pos = 0;
+
+ /* validate the binary data received from the wapsite */
+ if ( memcmp( MXIT_FRAME_MAGIC, &data[pos], strlen( MXIT_FRAME_MAGIC ) ) != 0 ) {
+ /* bad data, magic constant is wrong */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad magic)\n" );
+ goto done;
+ }
+ pos += strlen( MXIT_FRAME_MAGIC );
+
+ /* validate the image frame desc byte */
+ if ( data[pos] != '\x6F' ) {
+ /* bad frame desc */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad frame desc)\n" );
+ goto done;
+ }
+ pos++;
+
+ /* get the data length */
+ res = asn_getlength( &data[pos], &em_size );
+ if ( res <= 0 ) {
+ /* bad frame length */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad frame length)\n" );
+ goto done;
+ }
+ pos += res;
+#ifdef MXIT_DEBUG_EMO
+ purple_debug_info( MXIT_PLUGIN_ID, "read the length '%i'\n", em_size );
+#endif
+
+ /* utf-8 (emoticon name) */
+ res = asn_getUtf8( &data[pos], 0x0C, &str );
+ if ( res <= 0 ) {
+ /* bad utf-8 string */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad name string)\n" );
+ goto done;
+ }
+ pos += res;
+#ifdef MXIT_DEBUG_EMO
+ purple_debug_info( MXIT_PLUGIN_ID, "read the string '%s'\n", str );
+#endif
+ g_free( str );
+ str = NULL;
+
+ /* utf-8 (emoticon shortcut) */
+ res = asn_getUtf8( &data[pos], 0x81, &str );
+ if ( res <= 0 ) {
+ /* bad utf-8 string */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad shortcut string)\n" );
+ goto done;
+ }
+ pos += res;
+#ifdef MXIT_DEBUG_EMO
+ purple_debug_info( MXIT_PLUGIN_ID, "read the string '%s'\n", str );
+#endif
+ em_id = str;
+
+ /* validate the image data type */
+ if ( data[pos] != '\x82' ) {
+ /* bad frame desc */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad data type)\n" );
+ g_free( em_id );
+ goto done;
+ }
+ pos++;
+
+ /* get the data length */
+ res = asn_getlength( &data[pos], &em_size );
+ if ( res <= 0 ) {
+ /* bad frame length */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad data length)\n" );
+ g_free( em_id );
+ goto done;
+ }
+ pos += res;
+#ifdef MXIT_DEBUG_EMO
+ purple_debug_info( MXIT_PLUGIN_ID, "read the length '%i'\n", em_size );
+#endif
+
+ if ( g_hash_table_lookup( mx->session->iimages, em_id ) ) {
+ /* emoticon found in the table, so ignore this one */
+ goto done;
+ }
+
+ /* make a copy of the data */
+ em_data = g_malloc( em_size );
+ memcpy( em_data, &data[pos], em_size );
+
+ /* strip the mxit markup tags from the emoticon id */
+ if ( ( em_id[0] == '.' ) && ( em_id[1] == '{' ) ) {
+ parse_emoticon_str( &em_id[2], emo );
+ strcpy( em_id, emo );
+ }
+
+ /* we now have the emoticon, store it in the imagestore */
+ id = purple_imgstore_add_with_id( em_data, em_size, NULL );
+
+ /* map the mxit emoticon id to purple image id */
+ intptr = g_malloc( sizeof( int ) );
+ *intptr = id;
+ g_hash_table_insert( mx->session->iimages, em_id, intptr );
+
+ mx->flags |= PURPLE_MESSAGE_IMAGES;
+done:
+ mx->img_count--;
+ if ( ( mx->img_count == 0 ) && ( mx->converted ) ) {
+ /*
+ * this was the last outstanding emoticon for this message,
+ * so we can now display it to the user.
+ */
+ mxit_show_message( mx );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a request to the MXit WAP site to download the specified emoticon.
+ *
+ * @param mx The Markup message object
+ * @param id The ID for the emoticon
+ */
+static void emoticon_request( struct RXMsgData* mx, const char* id )
+{
+ PurpleUtilFetchUrlData* url_data;
+ const char* wapserver;
+ char* url;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "sending request for emoticon '%s'\n", id );
+
+ wapserver = purple_account_get_string( mx->session->acc, MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE );
+
+ /* reference: "libpurple/util.h" */
+ url = g_strdup_printf( "%s/res/?type=emo&mlh=%i&sc=%s&ts=%li", wapserver, MXIT_EMOTICON_SIZE, id, time( NULL ) );
+ url_data = purple_util_fetch_url_request( url, TRUE, NULL, TRUE, NULL, FALSE, emoticon_returned, mx );
+ g_free( url );
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse a Vibe command.
+ *
+ * @param mx The Markup message object
+ * @param message The message text (which contains the vibe)
+ * @return id The length of the message to skip
+ */
+static int mxit_parse_vibe( struct RXMsgData* mx, const char* message )
+{
+ int vibeid;
+
+ vibeid = message[2] - '0';
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Vibe received (%i)\n", vibeid );
+
+ if ( vibeid > ( ARRAY_SIZE( vibes ) - 1 ) ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "Unsupported vibe received (%i)\n", vibeid );
+ /* unsupported vibe */
+ return 0;
+ }
+
+ g_string_append_printf( mx->msg, "<font color=\"%s\"><i>%s Vibe...</i></font>", MXIT_VIBE_MSG_COLOR, vibes[vibeid] );
+ return 2;
+}
+
+
+/*------------------------------------------------------------------------
+ * Extract the nickname from a chatroom message and display it nicely in
+ * libPurple-style (HTML) markup.
+ *
+ * @param mx The received message data object
+ * @param message The message text
+ * @return The length of the message to skip
+ */
+static int mxit_extract_chatroom_nick( struct RXMsgData* mx, char* message, int len )
+{
+ int i;
+
+ if ( message[0] == '<' ) {
+ /*
+ * The message MIGHT contains an embedded nickname. But we can't
+ * be sure unless we find the end-of-nickname sequence: (>\n)
+ * Search for it....
+ */
+ gboolean found = FALSE;
+ gchar* nickname;
+
+ for ( i = 1; i < len; i++ ) {
+ if ( ( message[i] == '\n' ) && ( message[i-1] == '>' ) ) {
+ found = TRUE;
+ message[i-1] = '\0'; /* loose the '>' */
+ i++; /* and skip the new-line */
+ break;
+ }
+ }
+
+ if ( found ) {
+ /*
+ * The message definitely had an embedded nickname - generate a marked-up
+ * message to be displayed.
+ */
+ nickname = g_markup_escape_text( &message[1], -1 );
+
+ /* add nickname within some BOLD markup to the new converted message */
+ g_string_append_printf( mx->msg, "<b>%s:</b> ", nickname );
+
+ /* free up the resources */
+ g_free( nickname );
+
+ return i;
+ }
+ }
+
+ return 0;
+}
+
+
+
+/*------------------------------------------------------------------------
+ * Convert a message containing MXit protocol markup to libPurple-style (HTML) markup.
+ *
+ * @param mx The received message data object
+ * @param message The message text
+ * @param len The length of the message
+ */
+void mxit_parse_markup( struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags )
+{
+ char tmpstr1[128];
+ char* ch;
+ int i = 0;
+
+ /* tags */
+ gboolean tag_bold = FALSE;
+ gboolean tag_under = FALSE;
+ gboolean tag_italic = FALSE;
+
+#ifdef MXIT_DEBUG_MARKUP
+ purple_debug_info( MXIT_PLUGIN_ID, "Markup RX (original): '%s'\n", message );
+#endif
+
+
+ /*
+ * supported MXit markup:
+ * '*' bold
+ * '_' underline
+ * '/' italics
+ * '$' highlight text
+ * '.+' inc font size
+ * '.-' dec font size
+ * '#XXXXXX' foreground color
+ * '.{XX}' custom emoticon
+ * '\' escape the following character
+ * '::' MXit commands
+ */
+
+
+ if ( is_mxit_chatroom_contact( mx->session, mx->from ) ) {
+ /* chatroom message, so we need to extract and skip the sender's nickname
+ * which is embedded inside the message */
+ i = mxit_extract_chatroom_nick( mx, message, len );
+ }
+
+ /* run through the message and check for custom emoticons and markup */
+ for ( ; i < len; i++ ) {
+ switch ( message[i] ) {
+
+
+ /* mxit markup parsing */
+ case '*' :
+ if ( !( msgflags & CP_MSG_MARKUP ) ) {
+ g_string_append_c( mx->msg, message[i] );
+ break;
+ }
+
+ /* bold markup */
+ if ( !tag_bold )
+ g_string_append( mx->msg, "<b>" );
+ else
+ g_string_append( mx->msg, "</b>" );
+ tag_bold = !tag_bold;
+ break;
+ case '_' :
+ if ( !( msgflags & CP_MSG_MARKUP ) ) {
+ g_string_append_c( mx->msg, message[i] );
+ break;
+ }
+
+ /* underscore markup */
+ if ( !tag_under )
+ g_string_append( mx->msg, "<u>" );
+ else
+ g_string_append( mx->msg, "</u>" );
+ tag_under = !tag_under;
+ break;
+ case '/' :
+ if ( !( msgflags & CP_MSG_MARKUP ) ) {
+ g_string_append_c( mx->msg, message[i] );
+ break;
+ }
+
+ /* italics markup */
+ if ( !tag_italic )
+ g_string_append( mx->msg, "<i>" );
+ else
+ g_string_append( mx->msg, "</i>" );
+ tag_italic = !tag_italic;
+ break;
+ case '$' :
+ if ( !( msgflags & CP_MSG_MARKUP ) ) {
+ g_string_append_c( mx->msg, message[i] );
+ break;
+ }
+ else if ( i + 1 >= len ) {
+ /* message too short for complete link */
+ g_string_append_c( mx->msg, '$' );
+ break;
+ }
+
+ /* find the end tag */
+ ch = strstr( &message[i + 1], "$" );
+ if ( ch ) {
+ /* end found */
+ *ch = '\0';
+ mxit_add_html_link( mx, &message[i + 1], &message[i + 1] );
+ *ch = '$';
+ i += ( ch - &message[i + 1] ) + 1;
+ }
+ else {
+ g_string_append_c( mx->msg, message[i] );
+ }
+ /* highlight text */
+ break;
+ case '#' :
+ if ( !( msgflags & CP_MSG_MARKUP ) ) {
+ g_string_append_c( mx->msg, message[i] );
+ break;
+ }
+ else if ( i + COLORCODE_LEN >= len ) {
+ /* message too short for complete colour code */
+ g_string_append_c( mx->msg, '#' );
+ break;
+ }
+
+ /* foreground (text) color */
+ memcpy( tmpstr1, &message[i + 1], COLORCODE_LEN );
+ tmpstr1[ COLORCODE_LEN ] = '\0'; /* terminate string */
+ if ( strcmp( tmpstr1, "??????" ) == 0 ) {
+ /* need to reset the font */
+ g_string_append( mx->msg, "</font>" );
+ i += COLORCODE_LEN;
+ }
+ else if ( strspn( tmpstr1, "0123456789abcdefABCDEF") == COLORCODE_LEN ) {
+ /* definitely a numeric colour code */
+ g_string_append_printf( mx->msg, "<font color=\"#%s\">", tmpstr1 );
+ i += COLORCODE_LEN;
+ }
+ else {
+ /* not valid colour markup */
+ g_string_append_c( mx->msg, '#' );
+ }
+ break;
+ case '.' :
+ if ( !( msgflags & CP_MSG_EMOTICON ) ) {
+ g_string_append_c( mx->msg, message[i] );
+ break;
+ }
+ else if ( i + 1 >= len ) {
+ /* message too short */
+ g_string_append_c( mx->msg, '.' );
+ break;
+ }
+
+ switch ( message[i+1] ) {
+ case '+' :
+ /* increment text size */
+ g_string_append( mx->msg, "<font size=\"+1\">" );
+ i++;
+ break;
+ case '-' :
+ /* decrement text size */
+ g_string_append( mx->msg, "<font size=\"-1\">" );
+ i++;
+ break;
+ case '{' :
+ /* custom emoticon */
+ if ( i + 2 >= len ) {
+ /* message too short */
+ g_string_append_c( mx->msg, '.' );
+ break;
+ }
+
+ parse_emoticon_str( &message[i+2], tmpstr1 );
+ if ( tmpstr1[0] != '\0' ) {
+ mx->got_img = TRUE;
+
+ if ( g_hash_table_lookup( mx->session->iimages, tmpstr1 ) ) {
+ /* emoticon found in the cache, so we do not have to request it from the WAPsite */
+ }
+ else {
+ /* request emoticon from the WAPsite */
+ mx->img_count++;
+ emoticon_request( mx, tmpstr1 );
+ }
+
+ g_string_append_printf( mx->msg, MXIT_II_TAG"%s>", tmpstr1 );
+ i += strlen( tmpstr1 ) + 2;
+ }
+ else
+ g_string_append_c( mx->msg, '.' );
+
+ break;
+ default :
+ g_string_append_c( mx->msg, '.' );
+ break;
+ }
+ break;
+ case '\\' :
+ if ( i + 1 >= len ) {
+ /* message too short for an escaped character */
+ g_string_append_c( mx->msg, '\\' );
+ }
+ else {
+ /* ignore the next character, because its been escaped */
+ g_string_append_c( mx->msg, message[i + 1] );
+ i++;
+ }
+ break;
+
+
+ /* command parsing */
+ case ':' :
+ if ( i + 1 >= len ) {
+ /* message too short */
+ g_string_append_c( mx->msg, ':' );
+ break;
+ }
+
+ if ( message[i+1] == '@' ) {
+ /* this is a vibe! */
+ int size;
+
+ if ( i + 2 >= len ) {
+ /* message too short */
+ g_string_append_c( mx->msg, message[i] );
+ break;
+ }
+
+ size = mxit_parse_vibe( mx, &message[i] );
+ if ( size == 0 )
+ g_string_append_c( mx->msg, message[i] );
+ else
+ i += size;
+ }
+ else if ( msgtype != CP_MSGTYPE_COMMAND ) {
+ /* this is not a command message */
+ g_string_append_c( mx->msg, message[i] );
+ }
+ else if ( message[i+1] == ':' ) {
+ /* parse out the command */
+ int size;
+
+ size = mxit_parse_command( mx, &message[i] );
+ if ( size == 0 )
+ g_string_append_c( mx->msg, ':' );
+ else
+ i += size;
+ }
+ else {
+ g_string_append_c( mx->msg, ':' );
+ }
+ break;
+
+
+ /* these aren't MXit markup, but are interpreted by libPurple */
+ case '<' :
+ g_string_append( mx->msg, "&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;
+ }
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * 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:
+ * &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 */
+ 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 );
+}
diff --git a/libpurple/protocols/mxit/markup.h b/libpurple/protocols/mxit/markup.h
new file mode 100644
index 0000000000..bffc5b80d9
--- /dev/null
+++ b/libpurple/protocols/mxit/markup.h
@@ -0,0 +1,40 @@
+/*
+ * 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
+ */
+
+#ifndef _MXIT_MARKUP_H_
+#define _MXIT_MARKUP_H_
+
+#define MXIT_II_TAG "<MXII=" /* inline image placeholder string */
+
+
+void mxit_parse_markup( struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags );
+char* mxit_convert_markup_tx( const char* message, int* msgtype );
+void mxit_add_html_link( struct RXMsgData* mx, const char* linkname, const char* displayname );
+void mxit_show_message( struct RXMsgData* mx );
+
+void mxit_free_emoticon_cache( struct MXitSession* session );
+
+
+#endif /* _MXIT_MARKUP_H_ */
diff --git a/libpurple/protocols/mxit/multimx.c b/libpurple/protocols/mxit/multimx.c
new file mode 100644
index 0000000000..775af2597c
--- /dev/null
+++ b/libpurple/protocols/mxit/multimx.c
@@ -0,0 +1,597 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MultiMx GroupChat --
+ *
+ * Andrew Victor <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 <string.h>
+#include <errno.h>
+
+#include "purple.h"
+#include "prpl.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "multimx.h"
+#include "markup.h"
+
+
+#if 0
+static void multimx_dump(struct multimx* multimx)
+{
+ purple_debug_info(MXIT_PLUGIN_ID, "MultiMX:\n");
+ purple_debug_info(MXIT_PLUGIN_ID, " Chat ID: %i\n", multimx->chatid);
+ purple_debug_info(MXIT_PLUGIN_ID, " Username: %s\n", multimx->roomid);
+ purple_debug_info(MXIT_PLUGIN_ID, " Alias: %s\n", multimx->roomname);
+ purple_debug_info(MXIT_PLUGIN_ID, " State: %i\n", multimx->state);
+}
+#endif
+
+
+/*------------------------------------------------------------------------
+ * Find a MultiMx session based on libpurple chatID.
+ *
+ * @param session The MXit session object
+ * @param id The libpurple group-chat ID
+ * @return The MultiMX room object (or NULL if not found)
+ */
+static struct multimx* find_room_by_id(struct MXitSession* session, int id)
+{
+ GList* x = session->rooms;
+
+ while (x != NULL) {
+ struct multimx* multimx = (struct multimx *) x->data;
+
+ if (multimx->chatid == id)
+ return multimx;
+
+ x = g_list_next(x);
+ }
+
+ return NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Find a MultiMx session based on Alias
+ *
+ * @param session The MXit session object
+ * @param roomname The UI room-name
+ * @return The MultiMX room object (or NULL if not found)
+ */
+static struct multimx* find_room_by_alias(struct MXitSession* session, const char* roomname)
+{
+ GList* x = session->rooms;
+
+ while (x != NULL) {
+ struct multimx* multimx = (struct multimx *) x->data;
+
+ if (!strcmp(multimx->roomname, roomname))
+ return multimx;
+
+ x = g_list_next(x);
+ }
+
+ return NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Find a MultiMx session based on Username (MXit RoomId)
+ *
+ * @param session The MXit session object
+ * @param username The MXit RoomID (MultiMX contact username)
+ * @return The MultiMX room object (or NULL if not found)
+ */
+static struct multimx* find_room_by_username(struct MXitSession* session, const char* username)
+{
+ GList* x = session->rooms;
+
+ while (x != NULL) {
+ struct multimx* multimx = (struct multimx *) x->data;
+
+ if (!strcmp(multimx->roomid, username))
+ return multimx;
+
+ x = g_list_next(x);
+ }
+
+ return NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Create a GroupChat room, and add to list of rooms.
+ *
+ * @param session The MXit session object
+ * @param roomid The MXit RoomID (MultiMX contact username)
+ * @param roomname The UI room-name
+ * @param state The initial state of the room (see multimx.h)
+ * @return The MultiMX room object
+ */
+static struct multimx* room_create(struct MXitSession* session, const char* roomid, const char* roomname, short state)
+{
+ struct multimx* multimx = NULL;
+ static int groupchatID = 1;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat create - roomid='%s' roomname='%s'\n", roomid, roomname);
+
+ /* Create a new GroupChat */
+ multimx = g_new0(struct multimx, 1);
+
+ /* Initialize groupchat */
+ g_strlcpy(multimx->roomid, roomid, sizeof(multimx->roomid));
+ g_strlcpy(multimx->roomname, roomname, sizeof(multimx->roomname));
+ multimx->chatid = groupchatID++;
+ multimx->state = state;
+
+ /* Add to GroupChat list */
+ session->rooms = g_list_append(session->rooms, multimx);
+
+ return multimx;
+}
+
+
+/*------------------------------------------------------------------------
+ * Free the Groupchat room.
+ *
+ * @param session The MXit session object
+ * @param multimx The MultiMX room object to deallocate
+ */
+static void room_remove(struct MXitSession* session, struct multimx* multimx)
+{
+ /* Remove from GroupChat list */
+ session->rooms = g_list_remove(session->rooms, multimx);
+
+ /* Deallocate it */
+ free (multimx);
+ multimx = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Another user has join the GroupChat, add them to the member-list.
+ *
+ * @param session The MXit session object
+ * @param multimx The MultiMX room object
+ * @param nickname The nickname of the user who joined the room
+ */
+static void member_added(struct MXitSession* session, struct multimx* multimx, const char* nickname)
+{
+ PurpleConversation *convo;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "member_added: '%s'\n", nickname);
+
+ convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc);
+ if (convo == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname);
+ return;
+ }
+
+ purple_conv_chat_add_user(PURPLE_CONV_CHAT(convo), nickname, NULL, PURPLE_CBFLAGS_NONE, TRUE);
+}
+
+
+/*------------------------------------------------------------------------
+ * Another user has left the GroupChat, remove them from the member-list.
+ *
+ * @param session The MXit session object
+ * @param multimx The MultiMX room object
+ * @param nickname The nickname of the user who left the room
+ */
+static void member_removed(struct MXitSession* session, struct multimx* multimx, const char* nickname)
+{
+ PurpleConversation *convo;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "member_removed: '%s'\n", nickname);
+
+ convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc);
+ if (convo == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname);
+ return;
+ }
+
+ purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo), nickname, NULL);
+}
+
+
+/*------------------------------------------------------------------------
+ * Update the full GroupChat member list.
+ *
+ * @param session The MXit session object
+ * @param multimx The MultiMX room object
+ * @param data The nicknames of the users in the room (separated by \n)
+ */
+static void member_update(struct MXitSession* session, struct multimx* multimx, char* data)
+{
+ PurpleConversation *convo;
+ gchar** userlist;
+ int i = 0;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "member_update: '%s'\n", data);
+
+ convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc);
+ if (convo == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname);
+ return;
+ }
+
+ /* Clear list */
+ purple_conv_chat_clear_users(PURPLE_CONV_CHAT(convo));
+
+ /* Add each member */
+ data = g_strstrip(data); /* string leading & trailing whitespace */
+ userlist = g_strsplit(data, "\n", 0); /* tokenize string */
+ while (userlist[i] != NULL) {
+ purple_debug_info(MXIT_PLUGIN_ID, "member_update - adding: '%s'\n", userlist[i]);
+ purple_conv_chat_add_user(PURPLE_CONV_CHAT(convo), userlist[i], NULL, PURPLE_CBFLAGS_NONE, FALSE);
+ i++;
+ }
+ g_strfreev(userlist);
+}
+
+
+/* -------------------------------------------------------------------------------------------------
+ * Calls from MXit Protocol layer
+ * ------------------------------------------------------------------------------------------------- */
+
+/*------------------------------------------------------------------------
+ * Received a Subscription Request to a MultiMX room.
+ *
+ * @param session The MXit session object
+ * @param contact The invited MultiMX room's contact information
+ * @param creator The nickname of the room's creator / invitor
+ */
+void multimx_invite(struct MXitSession* session, struct contact* contact, const char* creator)
+{
+ GHashTable *components;
+ struct multimx* multimx = NULL;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat invite to '%s' by '%s'\n", contact->alias, creator);
+
+ /* Create a new room */
+ multimx = room_create(session, contact->username, contact->alias, STATE_INVITED);
+
+ components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+ g_hash_table_insert(components, g_strdup("room"), g_strdup(contact->alias));
+
+ /* Call libpurple - will trigger either 'mxit_chat_join' or 'mxit_chat_reject' */
+ serv_got_chat_invite(session->con, contact->alias, creator, NULL, components);
+}
+
+
+/*------------------------------------------------------------------------
+ * MultiMX room has been added to the roster.
+ *
+ * @param session The MXit session object
+ * @param contact The MultiMX room's contact information
+ */
+void multimx_created(struct MXitSession* session, struct contact* contact)
+{
+ PurpleConnection *gc = session->con;
+ struct multimx* multimx = NULL;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat '%s' created as '%s'\n", contact->alias, contact->username);
+
+ /* Find matching MultiMX group */
+ multimx = find_room_by_username(session, contact->username);
+ if (multimx == NULL) {
+ multimx = room_create(session, contact->username, contact->alias, TRUE);
+ }
+ else if (multimx->state == STATE_INVITED) {
+ /* After successfully accepting an invitation */
+ multimx->state = STATE_JOINED;
+ }
+
+ /* Call libpurple - will trigger 'mxit_chat_join' */
+ serv_got_joined_chat(gc, multimx->chatid, multimx->roomname);
+
+ /* Send ".list" command to GroupChat server to retrieve current member-list */
+ mxit_send_message(session, multimx->roomid, ".list", FALSE);
+}
+
+
+/*------------------------------------------------------------------------
+ * Is this username a MultiMX contact?
+ *
+ * @param session The MXit session object
+ * @param username The username of the contact
+ * @return TRUE if this contacts matches the RoomID of a MultiMX room.
+ */
+gboolean is_multimx_contact(struct MXitSession* session, const char* username)
+{
+ /* Check for username in list of open rooms */
+ return (find_room_by_username(session, username) != NULL);
+}
+
+
+/*------------------------------------------------------------------------
+ * Received a message from a MultiMX room.
+ *
+ */
+void multimx_message_received(struct RXMsgData* mx, char* msg, int msglen, short msgtype, int msgflags)
+{
+ struct multimx* multimx = NULL;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat message received: %s\n", msg);
+
+ /* Find matching multimx group */
+ multimx = find_room_by_username(mx->session, mx->from);
+ if (multimx == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Groupchat '%s' not found\n", mx->from);
+ return;
+ }
+
+ /* Determine if system message or a message from a contact */
+ if (msg[0] == '<') {
+ /* Message contains embedded nickname - must be from contact */
+ unsigned int i;
+
+ for (i = 1; i < strlen(msg); i++) { /* search for end of nickname */
+ if (msg[i] == '>') {
+ msg[i] = '\0';
+ g_free(mx->from);
+ mx->from = g_strdup(&msg[1]);
+ msg = &msg[i+2]; /* skip '>' and newline */
+ break;
+ }
+ }
+
+ /* now do markup processing on the message */
+ mx->chatid = multimx->chatid;
+ mxit_parse_markup(mx, msg, strlen(msg), msgtype, msgflags);
+ }
+ else {
+ /* Must be a service message */
+ char* ofs;
+
+ /* Determine if somebody has joined or left - update member-list */
+ if ((ofs = strstr(msg, " has joined")) != NULL) {
+ /* Somebody has joined */
+ *ofs = '\0';
+ member_added(mx->session, multimx, msg);
+ mx->processed = TRUE;
+ }
+ else if ((ofs = strstr(msg, " has left")) != NULL) {
+ /* Somebody has left */
+ *ofs = '\0';
+ member_removed(mx->session, multimx, msg);
+ mx->processed = TRUE;
+ }
+ else if (g_str_has_prefix(msg, "The following users are in this MultiMx:") == TRUE) {
+ member_update(mx->session, multimx, msg + strlen("The following users are in this MultiMx:") + 1);
+ mx->processed = TRUE;
+ }
+ else {
+ /* Display server message in chat window */
+ serv_got_chat_in(mx->session->con, multimx->chatid, "MXit", PURPLE_MESSAGE_SYSTEM, msg, mx->timestamp);
+ mx->processed = TRUE;
+ }
+ }
+}
+
+
+
+/* -------------------------------------------------------------------------------------------------
+ * Callbacks from libpurple
+ * ------------------------------------------------------------------------------------------------- */
+
+/*------------------------------------------------------------------------
+ * User has selected "Add Chat" from the main menu.
+ *
+ * @param gc The connection object
+ * @return A list of chat configuration values
+ */
+GList* mxit_chat_info(PurpleConnection *gc)
+{
+ GList *m = NULL;
+ struct proto_chat_entry *pce;
+
+ /* Configuration option: Room Name */
+ pce = g_new0(struct proto_chat_entry, 1);
+ pce->label = "_Room Name:";
+ pce->identifier = "room";
+ pce->required = TRUE;
+ m = g_list_append(m, pce);
+
+ return m;
+}
+
+
+/*------------------------------------------------------------------------
+ * User has joined a chatroom, either because they are creating it or they
+ * accepted an invite.
+ *
+ * @param gc The connection object
+ * @param components The list of chat configuration values
+ */
+void mxit_chat_join(PurpleConnection *gc, GHashTable *components)
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ const char* roomname = NULL;
+ struct multimx* multimx = NULL;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "mxit_chat_join\n");
+
+ /* Determine if groupchat already exists */
+ roomname = g_hash_table_lookup(components, "room");
+ multimx = find_room_by_alias(session, roomname);
+
+ if (multimx != NULL) {
+ /* The room information already exists */
+
+ if (multimx->state == STATE_INVITED) {
+ /* Invite is pending */
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i accept sent\n", multimx->chatid);
+
+ /* Send Subscription Accept to MXit */
+ mxit_send_allow_sub(session, multimx->roomid, multimx->roomname);
+ }
+ else {
+ /* Join existing room */
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i rejoined\n", multimx->chatid);
+
+ serv_got_joined_chat(gc, multimx->chatid, multimx->roomname);
+ }
+ }
+ else {
+ /* Send Groupchat Create to MXit */
+ mxit_send_groupchat_create(session, roomname, 0, NULL);
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * User has rejected an invite to join a MultiMX room.
+ *
+ * @param gc The connection object
+ * @param components The list of chat configuration values
+ */
+void mxit_chat_reject(PurpleConnection *gc, GHashTable* components)
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ const char* roomname = NULL;
+ struct multimx* multimx = NULL;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "mxit_chat_reject\n");
+
+ roomname = g_hash_table_lookup(components, "room");
+ multimx = find_room_by_alias(session, roomname);
+ if (multimx == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Groupchat '%s' not found\n", roomname);
+ return;
+ }
+
+ /* Send Subscription Reject to MXit */
+ mxit_send_deny_sub(session, multimx->roomid);
+
+ /* Remove from our list of rooms */
+ room_remove(session, multimx);
+}
+
+
+/*------------------------------------------------------------------------
+ * Return name of chatroom (on mouse hover)
+ *
+ * @param components The list of chat configuration values.
+ * @return The name of the chat room
+ */
+char* mxit_chat_name(GHashTable *components)
+{
+ return g_strdup(g_hash_table_lookup(components, "room"));
+}
+
+
+/*------------------------------------------------------------------------
+ * User has selected to invite somebody to a chatroom.
+ *
+ * @param gc The connection object
+ * @param id The chat room ID
+ * @param msg The invitation message entered by the user
+ * @param name The username of the person to invite
+ */
+void mxit_chat_invite(PurpleConnection *gc, int id, const char *msg, const char *username)
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ struct multimx* multimx = NULL;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat invite to '%s'\n", username);
+
+ /* Find matching MultiMX group */
+ multimx = find_room_by_id(session, id);
+ if (multimx == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Could not find groupchat %i\n", id);
+ return;
+ }
+
+ /* Send invite to MXit */
+ mxit_send_groupchat_invite(session, multimx->roomid, 1, &username);
+}
+
+
+/*------------------------------------------------------------------------
+ * User as closed the chat window, and the chatroom is not marked as persistent.
+ *
+ * @param gc The connection object
+ * @param id The chat room ID
+ */
+void mxit_chat_leave(PurpleConnection *gc, int id)
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ struct multimx* multimx = NULL;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i leave\n", id);
+
+ /* Find matching multimx group */
+ multimx = find_room_by_id(session, id);
+ if (multimx == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Could not find groupchat %i\n", id);
+ return;
+ }
+
+ /* Send Remove Groupchat to MXit */
+ mxit_send_remove(session, multimx->roomid);
+
+ /* Remove from our list of rooms */
+ room_remove(session, multimx);
+}
+
+
+/*------------------------------------------------------------------------
+ * User has entered a message in a chatroom window, send it to the MXit server.
+ *
+ * @param gc The connection object
+ * @param id The chat room ID
+ * @param message The sent message data
+ * @param flags The message flags
+ * @return Indicates success / failure
+ */
+int mxit_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags)
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ struct multimx* multimx = NULL;
+ const char* nickname;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i message send: '%s'\n", id, message);
+
+ /* Find matching MultiMX group */
+ multimx = find_room_by_id(session, id);
+ if (multimx == NULL) {
+ purple_debug_error(MXIT_PLUGIN_ID, "Could not find groupchat %i\n", id);
+ return -1;
+ }
+
+ /* Send packet to MXit */
+ mxit_send_message(session, multimx->roomid, message, TRUE);
+
+ /* Determine our nickname to display */
+ if (session->profile && (session->profile->nickname[0] != '\0')) /* default is profile name (since that's what everybody else sees) */
+ nickname = session->profile->nickname;
+ else
+ nickname = purple_account_get_alias(purple_connection_get_account(gc)); /* local alias */
+
+ /* Display message in chat window */
+ serv_got_chat_in(gc, id, nickname, flags, message, time(NULL));
+
+ return 0;
+}
+
diff --git a/libpurple/protocols/mxit/multimx.h b/libpurple/protocols/mxit/multimx.h
new file mode 100644
index 0000000000..868f91b216
--- /dev/null
+++ b/libpurple/protocols/mxit/multimx.h
@@ -0,0 +1,104 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MultiMx GroupChat --
+ *
+ * Andrew Victor <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
+ */
+
+#ifndef _MXIT_MULTIMX_H_
+#define _MXIT_MULTIMX_H_
+
+#include "roster.h"
+
+
+/* GroupChat Room state */
+#define STATE_CREATOR 0
+#define STATE_INVITED 1
+#define STATE_JOINED 2
+
+/*
+ * a MultiMX room
+ */
+struct multimx {
+ char roomname[MXIT_CP_MAX_ALIAS_LEN]; /* name of the room */
+ char roomid[MXIT_CP_MAX_JID_LEN]; /* internal JID for room */
+ int chatid; /* libpurple chat ID */
+ short state; /* state */
+};
+
+
+/*
+ * Received a Subscription Request to a MultiMX room.
+ */
+void multimx_invite(struct MXitSession* session, struct contact* contact, const char* creator);
+
+/*
+ * MultiMX room has been added to the roster.
+ */
+void multimx_created(struct MXitSession* session, struct contact* contact);
+
+/*
+ * Is this username a MultiMX contact?
+ */
+gboolean is_multimx_contact(struct MXitSession* session, const char* username);
+
+/*
+ * Received a message from a MultiMX room.
+ */
+void multimx_message_received(struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags);
+
+/*
+ * User has selected "Add Chat" from the main menu.
+ */
+GList* mxit_chat_info(PurpleConnection *gc);
+
+/*
+ * User has joined a chatroom, either because they are creating it or they accepted an invite.
+ */
+void mxit_chat_join(PurpleConnection *gc, GHashTable *data);
+
+/*
+ * User has rejected an invite to join a MultiMX room.
+ */
+void mxit_chat_reject(PurpleConnection *gc, GHashTable* components);
+
+/*
+ * Return name of chatroom (on mouse hover)
+ */
+char* mxit_chat_name(GHashTable *data);
+
+/*
+ * User has selected to invite somebody to a chatroom.
+ */
+void mxit_chat_invite(PurpleConnection *gc, int id, const char *msg, const char *name);
+
+/*
+ * User as closed the chat window, and the chatroom is not marked as persistent.
+ */
+void mxit_chat_leave(PurpleConnection *gc, int id);
+
+/*
+ * User has entered a message in a chatroom window, send it to the MXit server.
+ */
+int mxit_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags);
+
+
+#endif /* _MXIT_MULTIMX_H_ */
diff --git a/libpurple/protocols/mxit/mxit.c b/libpurple/protocols/mxit/mxit.c
new file mode 100644
index 0000000000..43d8a7a0c0
--- /dev/null
+++ b/libpurple/protocols/mxit/mxit.c
@@ -0,0 +1,694 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit libPurple plugin API --
+ *
+ * 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 <glib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "purple.h"
+#include "notify.h"
+#include "plugin.h"
+#include "version.h"
+
+#include "mxit.h"
+#include "protocol.h"
+#include "login.h"
+#include "roster.h"
+#include "chunk.h"
+#include "filexfer.h"
+#include "actions.h"
+#include "multimx.h"
+
+
+#ifdef MXIT_LINK_CLICK
+
+
+/* pidgin callback function pointers for URI click interception */
+static void *(*mxit_pidgin_uri_cb)(const char *uri);
+static PurpleNotifyUiOps* mxit_nots_override_original;
+static PurpleNotifyUiOps mxit_nots_override;
+static int not_link_ref_count = 0;
+
+
+/*------------------------------------------------------------------------
+ * Handle an URI clicked on the UI
+ *
+ * @param link the link name which has been clicked
+ */
+static void* mxit_link_click( const char* link64 )
+{
+ PurpleAccount* account;
+ PurpleConnection* con;
+ gchar** parts = NULL;
+ gchar* link = NULL;
+ unsigned int len;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_link_click (%s)\n", link64 );
+
+ if ( g_ascii_strncasecmp( link64, MXIT_LINK_PREFIX, strlen( MXIT_LINK_PREFIX ) ) != 0 ) {
+ /* this is not for us */
+ goto skip;
+ }
+
+ /* decode the base64 payload */
+ link = (gchar*) purple_base64_decode( link64 + strlen( MXIT_LINK_PREFIX ), &len );
+ purple_debug_info( MXIT_PLUGIN_ID, "Clicked Link: '%s'\n", link );
+
+ parts = g_strsplit( link, "|", 5 );
+
+ /* check if this is a valid mxit link */
+ if ( ( !parts ) || ( !parts[0] ) || ( !parts[1] ) || ( !parts[2] ) || ( !parts[3] ) || ( !parts[4] ) ) {
+ /* this is not for us */
+ goto skip;
+ }
+ else if ( g_ascii_strcasecmp( parts[0], MXIT_LINK_KEY ) != 0 ) {
+ /* this is not for us */
+ goto skip;
+ }
+
+ /* find the account */
+ account = purple_accounts_find( parts[1], parts[2] );
+ if ( !account )
+ goto skip;
+ con = purple_account_get_connection( account );
+
+ /* send click message back to MXit */
+ mxit_send_message( con->proto_data, parts[3], parts[4], FALSE );
+
+ g_free( link );
+ link = NULL;
+ g_strfreev( parts );
+ parts = NULL;
+
+ return (void*) link64;
+
+skip:
+ /* this is not an internal mxit link */
+
+ if ( link )
+ g_free( link );
+ link = NULL;
+
+ if ( parts )
+ g_strfreev( parts );
+ parts = NULL;
+
+ if ( mxit_pidgin_uri_cb )
+ return mxit_pidgin_uri_cb( link64 );
+ else
+ return (void*) link64;
+}
+
+
+/*------------------------------------------------------------------------
+ * Register MXit to receive URI click notifications from the UI
+ */
+void mxit_register_uri_handler()
+{
+ not_link_ref_count++;
+ if ( not_link_ref_count == 1 ) {
+ /* make copy of notifications */
+ mxit_nots_override_original = purple_notify_get_ui_ops();
+ memcpy( &mxit_nots_override, mxit_nots_override_original, sizeof( PurpleNotifyUiOps ) );
+
+ /* save previously configured callback function pointer */
+ mxit_pidgin_uri_cb = mxit_nots_override.notify_uri;
+
+ /* override the URI function call with MXit's own one */
+ mxit_nots_override.notify_uri = mxit_link_click;
+ purple_notify_set_ui_ops( &mxit_nots_override );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Unegister MXit from receiving URI click notifications from the UI
+ */
+static void mxit_unregister_uri_handler()
+{
+ not_link_ref_count--;
+ if ( not_link_ref_count == 0 ) {
+ /* restore the notifications to its original state */
+ purple_notify_set_ui_ops( mxit_nots_override_original );
+ }
+}
+
+#endif
+
+
+/*------------------------------------------------------------------------
+ * This gets called when a new chat conversation is opened by the user
+ *
+ * @param conv The conversation object
+ * @param session The MXit session object
+ */
+static void mxit_cb_chat_created( PurpleConversation* conv, struct MXitSession* session )
+{
+ PurpleConnection* gc;
+ struct contact* contact;
+ PurpleBuddy* buddy;
+ const char* who;
+
+ gc = purple_conversation_get_gc( conv );
+ if ( session->con != gc ) {
+ /* not our conversation */
+ return;
+ }
+ else if ( purple_conversation_get_type( conv ) != PURPLE_CONV_TYPE_IM ) {
+ /* wrong type of conversation */
+ return;
+ }
+
+ /* get the contact name */
+ who = purple_conversation_get_name( conv );
+ if ( !who )
+ return;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Conversation started with '%s'\n", who );
+
+ /* find the buddy object */
+ buddy = purple_find_buddy( session->acc, who );
+ if ( ( !buddy ) || ( !buddy->proto_data ) )
+ return;
+
+ /* we ignore all conversations with which we have chatted with in this session */
+ if ( find_active_chat( session->active_chats, who ) )
+ return;
+
+ /* determite if this buddy is a MXit service */
+ contact = buddy->proto_data;
+ switch ( contact->type ) {
+ case MXIT_TYPE_BOT :
+ case MXIT_TYPE_CHATROOM :
+ case MXIT_TYPE_GALLERY :
+ case MXIT_TYPE_INFO :
+ serv_got_im( session->con, who, "<font color=\"#999999\">Loading menu...</font>\n", PURPLE_MESSAGE_NOTIFY, time( NULL ) );
+ mxit_send_message( session, who, " ", FALSE );
+ default :
+ break;
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Enable some signals to handled by our plugin
+ *
+ * @param session The MXit session object
+ */
+void mxit_enable_signals( struct MXitSession* session )
+{
+ /* enable the signal when a new conversation is opened by the user */
+ purple_signal_connect_priority( purple_conversations_get_handle(), "conversation-created", session, PURPLE_CALLBACK( mxit_cb_chat_created ),
+ session, PURPLE_SIGNAL_PRIORITY_HIGHEST );
+}
+
+
+/*------------------------------------------------------------------------
+ * Disable some signals handled by our plugin
+ *
+ * @param session The MXit session object
+ */
+static void mxit_disable_signals( struct MXitSession* session )
+{
+ /* disable the signal when a new conversation is opened by the user */
+ purple_signal_disconnect( purple_conversations_get_handle(), "conversation-created", session, PURPLE_CALLBACK( mxit_cb_chat_created ) );
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the base icon name.
+ *
+ * @param account The MXit account object
+ * @param buddy The buddy
+ * @return The icon name (excluding extension)
+ */
+static const char* mxit_list_icon( PurpleAccount* account, PurpleBuddy* buddy )
+{
+ return "mxit";
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the emblem icon name.
+ *
+ * @param buddy The buddy
+ * @return The icon name (excluding extension)
+ */
+static const char* mxit_list_emblem( PurpleBuddy* buddy )
+{
+ struct contact* contact = buddy->proto_data;
+
+ if ( !contact )
+ return NULL;
+
+ switch ( contact-> type ) {
+ case MXIT_TYPE_JABBER : /* external contacts via MXit */
+ case MXIT_TYPE_MSN :
+ case MXIT_TYPE_YAHOO :
+ case MXIT_TYPE_ICQ :
+ case MXIT_TYPE_AIM :
+ case MXIT_TYPE_QQ :
+ case MXIT_TYPE_WV :
+ return "external";
+
+ case MXIT_TYPE_BOT : /* MXit services */
+ case MXIT_TYPE_GALLERY :
+ case MXIT_TYPE_INFO :
+ return "bot";
+
+ case MXIT_TYPE_CHATROOM : /* MXit group chat services */
+ case MXIT_TYPE_MULTIMX :
+ default:
+ return NULL;
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Return short string representing buddy's status for display on buddy list.
+ * Returns status message (if one is set), or otherwise the mood.
+ *
+ * @param buddy The buddy.
+ * @return The status text
+ */
+char* mxit_status_text( PurpleBuddy* buddy )
+{
+ struct contact* contact = buddy->proto_data;
+
+ if ( !contact )
+ return NULL;
+
+ if ( contact->statusMsg ) {
+ /* status message */
+ return g_strdup( contact-> statusMsg );
+ }
+ else {
+ /* mood */
+ return g_strdup( mxit_convert_mood_to_name( contact->mood ) );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Return UI tooltip information for a buddy when hovering in buddy list.
+ *
+ * @param buddy The buddy
+ * @param info The tooltip info being returned
+ * @param full Return full or summarized information
+ */
+static void mxit_tooltip( PurpleBuddy* buddy, PurpleNotifyUserInfo* info, gboolean full )
+{
+ struct contact* contact = buddy->proto_data;
+
+ if ( !contact )
+ return;
+
+ /* status (reference: "libpurple/notify.h") */
+ if ( contact->presence != MXIT_PRESENCE_OFFLINE )
+ purple_notify_user_info_add_pair( info, _( "Status" ), mxit_convert_presence_to_name( contact->presence ) );
+
+ /* status message */
+ if ( contact->statusMsg )
+ purple_notify_user_info_add_pair( info, _( "Status Message" ), contact->statusMsg );
+
+ /* mood */
+ if ( contact->mood != MXIT_MOOD_NONE )
+ purple_notify_user_info_add_pair( info, _( "Mood" ), mxit_convert_mood_to_name( contact->mood ) );
+
+ /* subscription type */
+ if ( contact->subtype != 0 )
+ purple_notify_user_info_add_pair( info, _( "Subscription" ), mxit_convert_subtype_to_name( contact->subtype ) );
+
+ /* hidden number */
+ if ( contact->flags & MXIT_CFLAG_HIDDEN )
+ purple_notify_user_info_add_pair( info, _( "Hidden Number" ), "Yes" );
+}
+
+
+/*------------------------------------------------------------------------
+ * Initiate the logout sequence, close the connection and clear the session data.
+ *
+ * @param gc The connection object
+ */
+static void mxit_close( PurpleConnection* gc )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+
+ /* disable signals */
+ mxit_disable_signals( session );
+
+ /* close the connection */
+ mxit_close_connection( session );
+
+#ifdef MXIT_LINK_CLICK
+ /* unregister for uri click notification */
+ mxit_unregister_uri_handler();
+#endif
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Releasing the session object..\n" );
+
+ /* free the session memory */
+ g_free( session );
+ session = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a message to a contact
+ *
+ * @param gc The connection object
+ * @param who The username of the recipient
+ * @param message The message text
+ * @param flags Message flags (defined in conversation.h)
+ * @return Positive value (success, and echo to conversation window)
+ Zero (success, no echo)
+ Negative value (error)
+ */
+static int mxit_send_im( PurpleConnection* gc, const char* who, const char* message, PurpleMessageFlags flags )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "Sending message '%s' to buddy '%s'\n", message, who );
+
+ mxit_send_message( gc->proto_data, who, message, TRUE );
+
+ return 1; /* echo to conversation window */
+}
+
+
+/*------------------------------------------------------------------------
+ * The user changed their current presence state.
+ *
+ * @param account The MXit account object
+ * @param status The new status (libPurple status type)
+ */
+static void mxit_set_status( PurpleAccount* account, PurpleStatus* status )
+{
+ struct MXitSession* session = purple_account_get_connection( account )->proto_data;
+ const char* statusid;
+ int presence;
+ char* statusmsg1;
+ char* statusmsg2;
+
+ /* get the status id (reference: "libpurple/status.h") */
+ statusid = purple_status_get_id( status );
+
+ /* convert the purple status to a mxit status */
+ presence = mxit_convert_presence( statusid );
+ if ( presence < 0 ) {
+ /* error, status not found */
+ purple_debug_info( MXIT_PLUGIN_ID, "Presence status NOT found! (id = %s)\n", statusid );
+ return;
+ }
+
+ statusmsg1 = purple_markup_strip_html( purple_status_get_attr_string( status, "message" ) );
+ statusmsg2 = g_strndup( statusmsg1, CP_MAX_STATUS_MSG );
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_set_status: '%s'\n", statusmsg2 );
+
+ /* update presence state */
+ mxit_send_presence( session, presence, statusmsg2 );
+
+ g_free( statusmsg1 );
+ g_free( statusmsg2 );
+}
+
+
+/*------------------------------------------------------------------------
+ * MXit supports messages to offline contacts.
+ *
+ * @param buddy The buddy
+ */
+static gboolean mxit_offline_message( const PurpleBuddy *buddy )
+{
+ return TRUE;
+}
+
+
+/*------------------------------------------------------------------------
+ * Free the resources used to store a buddy.
+ *
+ * @param buddy The buddy
+ */
+static void mxit_free_buddy( PurpleBuddy* buddy )
+{
+ struct contact* contact;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_free_buddy\n" );
+
+ contact = buddy->proto_data;
+ if ( contact ) {
+ if ( contact->statusMsg )
+ g_free( contact->statusMsg );
+ if ( contact->avatarId )
+ g_free( contact->avatarId );
+ g_free( contact );
+ }
+ buddy->proto_data = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Periodic task called every KEEPALIVE_INTERVAL (30 sec) to to maintain
+ * idle connections, timeouts and the transmission queue to the MXit server.
+ *
+ * @param gc The connection object
+ */
+static void mxit_keepalive( PurpleConnection *gc )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+
+ /* if not logged in, there is nothing to do */
+ if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) )
+ return;
+
+ /* pinging is only for socket connections (HTTP does polling) */
+ if ( session->http )
+ return;
+
+ if ( session->last_tx <= time( NULL ) - MXIT_PING_INTERVAL ) {
+ /*
+ * this connection has been idle for too long, better ping
+ * the server before it kills our connection.
+ */
+ mxit_send_ping( session );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Set or clear our Buddy icon.
+ *
+ * @param gc The connection object
+ * @param img The buddy icon data
+ */
+static void mxit_set_buddy_icon( PurpleConnection *gc, PurpleStoredImage *img )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+
+ if ( img == NULL )
+ mxit_set_avatar( session, NULL, 0 );
+ else
+ mxit_set_avatar( session, purple_imgstore_get_data( img ), purple_imgstore_get_size( img ) );
+}
+
+
+/*------------------------------------------------------------------------
+ * Request profile information for another MXit contact.
+ *
+ * @param gc The connection object
+ * @param who The username of the contact.
+ */
+static void mxit_get_info( PurpleConnection *gc, const char *who )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ const char* profilelist[] = { CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_HIDENUMBER, CP_PROFILE_FULLNAME,
+ CP_PROFILE_TITLE, CP_PROFILE_FIRSTNAME, CP_PROFILE_LASTNAME, CP_PROFILE_EMAIL };
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_get_info: '%s'\n", who );
+
+
+ /* send profile request */
+ mxit_send_extprofile_request( session, who, ARRAY_SIZE( profilelist ), profilelist );
+}
+
+
+/*------------------------------------------------------------------------
+ * Return a list of labels to be used by Pidgin for assisting the user.
+ */
+static GHashTable* mxit_get_text_table( PurpleAccount* acc )
+{
+ GHashTable* table;
+
+ table = g_hash_table_new( g_str_hash, g_str_equal );
+
+ g_hash_table_insert( table, "login_label", _( "Your Mobile Number..." ) );
+
+ return table;
+}
+
+/*========================================================================================================================*/
+
+static PurplePluginProtocolInfo proto_info = {
+ OPT_PROTO_REGISTER_NOSCREENNAME | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_IM_IMAGE, /* options */
+ NULL, /* user_splits */
+ NULL, /* protocol_options */
+ { /* icon_spec */
+ "png", /* format */
+ 32, 32, /* min width & height */
+ MXIT_AVATAR_SIZE, /* max width */
+ MXIT_AVATAR_SIZE, /* max height */
+ 100000, /* max filezize */
+ PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY /* scaling rules */
+ },
+ mxit_list_icon, /* list_icon */
+ mxit_list_emblem, /* list_emblem */
+ mxit_status_text, /* status_text */
+ mxit_tooltip, /* tooltip_text */
+ mxit_status_types, /* status types [roster.c] */
+ NULL, /* blist_node_menu */
+ mxit_chat_info, /* chat_info [multimx.c] */
+ NULL, /* chat_info_defaults */
+ mxit_login, /* login [login.c] */
+ mxit_close, /* close */
+ mxit_send_im, /* send_im */
+ NULL, /* set_info */
+ NULL, /* send_typing */
+ mxit_get_info, /* get_info */
+ mxit_set_status, /* set_status */
+ NULL, /* set_idle */
+ NULL, /* change_passwd */
+ mxit_add_buddy, /* add_buddy [roster.c] */
+ NULL, /* add_buddies */
+ mxit_remove_buddy, /* remove_buddy [roster.c] */
+ NULL, /* remove_buddies */
+ NULL, /* add_permit */
+ NULL, /* add_deny */
+ NULL, /* rem_permit */
+ NULL, /* rem_deny */
+ NULL, /* set_permit_deny */
+ mxit_chat_join, /* join_chat [multimx.c] */
+ mxit_chat_reject, /* reject chat invite [multimx.c] */
+ mxit_chat_name, /* get_chat_name [multimx.c] */
+ mxit_chat_invite, /* chat_invite [multimx.c] */
+ mxit_chat_leave, /* chat_leave [multimx.c] */
+ NULL, /* chat_whisper */
+ mxit_chat_send, /* chat_send [multimx.c] */
+ mxit_keepalive, /* keepalive */
+ mxit_register, /* register_user */
+ NULL, /* get_cb_info */
+ NULL, /* get_cb_away */
+ mxit_buddy_alias, /* alias_buddy [roster.c] */
+ mxit_buddy_group, /* group_buddy [roster.c] */
+ mxit_rename_group, /* rename_group [roster.c] */
+ mxit_free_buddy, /* buddy_free */
+ NULL, /* convo_closed */
+ NULL, /* normalize */
+ mxit_set_buddy_icon, /* set_buddy_icon */
+ NULL, /* remove_group */ // TODO: Add function to move all contacts out of this group (cmd=30 - remove group)?
+ NULL, /* get_cb_real_name */
+ NULL, /* set_chat_topic */
+ NULL, /* find_blist_chat */
+ NULL, /* roomlist_get_list */
+ NULL, /* roomlist_cancel */
+ NULL, /* roomlist_expand_category */
+ mxit_xfer_enabled, /* can_receive_file [filexfer.c] */
+ mxit_xfer_tx, /* send_file [filexfer.c */
+ mxit_xfer_new, /* new_xfer [filexfer.c] */
+ mxit_offline_message, /* offline_message */
+ NULL, /* whiteboard_prpl_ops */
+ NULL, /* send_raw */
+ NULL, /* roomlist_room_serialize */
+ NULL, /* unregister_user */
+ NULL, /* send_attention */
+ NULL, /* attention_types */
+ sizeof( PurplePluginProtocolInfo ), /* struct_size */
+ mxit_get_text_table, /* get_account_text_table */
+ NULL,
+ NULL
+};
+
+
+static PurplePluginInfo plugin_info = {
+ PURPLE_PLUGIN_MAGIC, /* purple magic, this must always be PURPLE_PLUGIN_MAGIC */
+ PURPLE_MAJOR_VERSION, /* libpurple version */
+ PURPLE_MINOR_VERSION, /* libpurple version */
+ PURPLE_PLUGIN_PROTOCOL, /* plugin type (connecting to another network) */
+ NULL, /* UI requirement (NULL for core plugin) */
+ 0, /* plugin flags (zero is default) */
+ NULL, /* plugin dependencies (set this value to NULL no matter what) */
+ PURPLE_PRIORITY_DEFAULT, /* libpurple priority */
+
+ MXIT_PLUGIN_ID, /* plugin id (must be unique) */
+ MXIT_PLUGIN_NAME, /* plugin name (this will be displayed in the UI) */
+ MXIT_PLUGIN_VERSION, /* version of the plugin */
+
+ MXIT_PLUGIN_SUMMARY, /* short summary of the plugin */
+ MXIT_PLUGIN_DESC, /* description of the plugin (can be long) */
+ MXIT_PLUGIN_EMAIL, /* plugin author name and email address */
+ MXIT_PLUGIN_WWW, /* plugin website (to find new versions and reporting of bugs) */
+
+ NULL, /* function pointer for loading the plugin */
+ NULL, /* function pointer for unloading the plugin */
+ NULL, /* function pointer for destroying the plugin */
+
+ NULL, /* pointer to an UI-specific struct */
+ &proto_info, /* pointer to either a PurplePluginLoaderInfo or PurplePluginProtocolInfo struct */
+ NULL, /* pointer to a PurplePluginUiInfo struct */
+ mxit_actions, /* function pointer where you can define plugin-actions */
+
+ /* padding */
+ NULL, /* pointer reserved for future use */
+ NULL, /* pointer reserved for future use */
+ NULL, /* pointer reserved for future use */
+ NULL /* pointer reserved for future use */
+};
+
+
+/*------------------------------------------------------------------------
+ * Initialising the MXit plugin.
+ *
+ * @param plugin The plugin object
+ */
+static void init_plugin( PurplePlugin* plugin )
+{
+ PurpleAccountOption* option;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Loading MXit libPurple plugin...\n" );
+
+ /* Configuration options */
+
+ /* WAP server (reference: "libpurple/accountopt.h") */
+ option = purple_account_option_string_new( _( "WAP Server" ), MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE );
+ proto_info.protocol_options = g_list_append( proto_info.protocol_options, option );
+
+ option = purple_account_option_bool_new( _( "Connect via HTTP" ), MXIT_CONFIG_USE_HTTP, FALSE );
+ proto_info.protocol_options = g_list_append( proto_info.protocol_options, option );
+
+ option = purple_account_option_bool_new( _( "Enable splash-screen popup" ), MXIT_CONFIG_SPLASHPOPUP, FALSE );
+ proto_info.protocol_options = g_list_append( proto_info.protocol_options, option );
+
+ g_assert( sizeof( struct raw_chunk ) == 5 );
+}
+
+PURPLE_INIT_PLUGIN( mxit, init_plugin, plugin_info );
+
diff --git a/libpurple/protocols/mxit/mxit.h b/libpurple/protocols/mxit/mxit.h
new file mode 100644
index 0000000000..6f574af86c
--- /dev/null
+++ b/libpurple/protocols/mxit/mxit.h
@@ -0,0 +1,197 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit libPurple plugin API --
+ *
+ * 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
+ */
+
+#ifndef _MXIT_H_
+#define _MXIT_H_
+
+
+/* internationalize feedback strings */
+#ifndef _
+#ifdef GETTEXT_PACKAGE
+#include <glib/gi18n-lib.h>
+#else
+#define _( x ) ( x )
+#endif
+#endif
+
+
+#if defined( __APPLE__ )
+/* apple architecture */
+#ifndef HOST_NAME_MAX
+#define HOST_NAME_MAX 512
+#endif
+#elif defined( _WIN32 )
+/* windows architecture */
+#define HOST_NAME_MAX 512
+#include "libc_interface.h"
+#elif defined( __linux__ )
+/* linux architecture */
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#else
+/* other architecture */
+#ifndef HOST_NAME_MAX
+#define HOST_NAME_MAX 512
+#endif
+#endif
+
+
+#include "protocol.h"
+#include "profile.h"
+
+
+/* Plugin details */
+#define MXIT_PLUGIN_ID "prpl-loubserp-mxit"
+#define MXIT_PLUGIN_NAME "MXit"
+#define MXIT_PLUGIN_VERSION "2.2.0"
+#define MXIT_PLUGIN_EMAIL "Pieter Loubser <libpurple@mxit.com>"
+#define MXIT_PLUGIN_WWW "http://www.mxit.com"
+#define MXIT_PLUGIN_SUMMARY "MXit Protocol Plugin"
+#define MXIT_PLUGIN_DESC "MXit"
+
+#define MXIT_HTTP_USERAGENT "libpurple-"MXIT_PLUGIN_VERSION
+
+
+/* default connection settings */
+#define DEFAULT_SERVER "stream.mxit.co.za"
+#define DEFAULT_PORT 9119
+#define DEFAULT_WAPSITE "http://www.mxit.com"
+#define DEFAULT_HTTP_SERVER "http://int.poll.mxit.com:80/mxit"
+
+
+/* Purple account configuration variable names */
+#define MXIT_CONFIG_STATE "state"
+#define MXIT_CONFIG_WAPSERVER "wap_server"
+#define MXIT_CONFIG_DISTCODE "distcode"
+#define MXIT_CONFIG_CLIENTKEY "clientkey"
+#define MXIT_CONFIG_DIALCODE "dialcode"
+#define MXIT_CONFIG_SERVER_ADDR "server"
+#define MXIT_CONFIG_SERVER_PORT "port"
+#define MXIT_CONFIG_HTTPSERVER "httpserver"
+#define MXIT_CONFIG_SPLASHID "splashid"
+#define MXIT_CONFIG_SPLASHCLICK "splashclick"
+#define MXIT_CONFIG_SPLASHPOPUP "splashpopup"
+#define MXIT_CONFIG_COUNTRYCODE "cc"
+#define MXIT_CONFIG_LOCALE "locale"
+#define MXIT_CONFIG_USE_HTTP "use_http"
+
+
+/* account states */
+#define MXIT_STATE_LOGIN 0x00
+#define MXIT_STATE_REGISTER1 0x01
+#define MXIT_STATE_REGISTER2 0x02
+
+
+/* Client session flags */
+#define MXIT_FLAG_CONNECTED 0x01 /* established connection to the server */
+#define MXIT_FLAG_LOGGEDIN 0x02 /* user currently logged in */
+#define MXIT_FLAG_FIRSTROSTER 0x04 /* set to true once the first roster update has been recevied and processed */
+
+
+/* define this to enable the link clicking support */
+#define MXIT_LINK_CLICK
+
+
+#ifdef MXIT_LINK_CLICK
+#define MXIT_LINK_PREFIX "gopher://"
+#define MXIT_LINK_KEY "MXIT"
+#endif
+
+
+#define ARRAY_SIZE( x ) ( sizeof( x ) / sizeof( x[0] ) )
+
+
+/*
+ * data structure containing all MXit session information
+ */
+struct MXitSession {
+ /* socket connection */
+ char server[HOST_NAME_MAX]; /* MXit server name to connect to */
+ int port; /* MXit server port to connect on */
+ int fd; /* connection file descriptor */
+
+ /* http connection */
+ gboolean http; /* connect to MXit via HTTP and not by socket */
+ char http_server[HOST_NAME_MAX]; /* MXit HTTP server */
+ unsigned int http_sesid; /* HTTP session id */
+ unsigned int http_seqno; /* HTTP request sequence number */
+ guint http_timer_id; /* timer resource id (pidgin) */
+ int http_interval; /* poll inverval */
+ time_t http_last_poll; /* the last time a poll has been sent */
+ guint http_handler; /* HTTP connection handler */
+ void* http_out_req; /* HTTP outstanding request */
+
+ /* client */
+ struct login_data* logindata;
+ char* encpwd; /* encrypted password */
+ char distcode[64]; /* distribution code */
+ char clientkey[16]; /* client key */
+ char dialcode[8]; /* dialing code */
+ short flags; /* client session flags (see above) */
+
+ /* personal (profile) */
+ struct MXitProfile* profile; /* user's profile information */
+ int mood; /* user's current mood */
+
+ /* libpurple */
+ PurpleAccount* acc; /* pointer to the libpurple internal account struct */
+ PurpleConnection* con; /* pointer to the libpurple internal connection struct */
+
+ /* transmit */
+ struct tx_queue queue; /* transmit packet queue (FIFO mode) */
+ time_t last_tx; /* timestamp of last packet sent */
+ int outack; /* outstanding ack packet */
+ guint q_timer; /* timer handler for managing queue */
+
+ /* receive */
+ char rx_lbuf[16]; /* receive byte buffer (socket packet length) */
+ char rx_dbuf[CP_MAX_PACKET]; /* receive byte buffer (raw data) */
+ unsigned int rx_i; /* receive buffer current index */
+ int rx_res; /* amount of bytes still outstanding for the current packet */
+ char rx_state; /* current receiver state */
+ time_t last_rx; /* timestamp of last packet received */
+ GList* active_chats; /* list of all our contacts we received messages from (active chats) */
+
+ /* groupchat */
+ GList* rooms; /* active groupchat rooms */
+
+ /* inline images */
+ GHashTable* iimages; /* table which maps inline images (including emoticons) to purple's imgstore id's */
+};
+
+
+char* mxit_status_text( PurpleBuddy* buddy );
+void mxit_enable_signals( struct MXitSession* session );
+
+#ifdef MXIT_LINK_CLICK
+void mxit_register_uri_handler();
+#endif
+
+
+#endif /* _MXIT_H_ */
+
diff --git a/libpurple/protocols/mxit/profile.c b/libpurple/protocols/mxit/profile.c
new file mode 100644
index 0000000000..321a394a4f
--- /dev/null
+++ b/libpurple/protocols/mxit/profile.c
@@ -0,0 +1,160 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- user profile's --
+ *
+ * Andrew Victor <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 <ctype.h>
+#include <string.h>
+
+#include "purple.h"
+
+#include "mxit.h"
+#include "profile.h"
+#include "roster.h"
+
+
+/*------------------------------------------------------------------------
+ * Returns true if it is a valid date.
+ *
+ * @param bday Date-of-Birth string
+ * @return TRUE if valid, else FALSE
+ */
+gboolean validateDate( const char* bday )
+{
+ struct tm* tm;
+ time_t t;
+ int cur_year;
+ int max_days[13] = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ char date[16];
+ int year;
+ int month;
+ int day;
+
+ /* validate length */
+ if ( strlen( bday ) != 10 ) {
+ return FALSE;
+ }
+
+ /* validate the format */
+ if ( ( !isdigit( bday[0] ) ) || ( !isdigit( bday[1] ) ) || ( !isdigit( bday[2] ) ) || ( !isdigit( bday[3] ) ) || /* year */
+ ( bday[4] != '-' ) ||
+ ( !isdigit( bday[5] ) ) || ( !isdigit( bday[6] ) ) || /* month */
+ ( bday[7] != '-' ) ||
+ ( !isdigit( bday[8] ) ) || ( !isdigit( bday[9] ) ) ) { /* day */
+ return FALSE;
+ }
+
+ /* convert */
+ t = time( NULL );
+ tm = gmtime( &t );
+ cur_year = tm->tm_year + 1900;
+ memcpy( date, bday, 10 );
+ date[4] = '\0';
+ date[7] = '\0';
+ date[10] = '\0';
+ year = atoi( &date[0] );
+ month = atoi( &date[5] );
+ day = atoi( &date[8] );
+
+ /* validate month */
+ if ( ( month < 1 ) || ( month > 12 ) ) {
+ return FALSE;
+ }
+
+ /* validate day */
+ if ( ( day < 1 ) || ( day > max_days[month] ) ) {
+ return FALSE;
+ }
+
+ /* validate year */
+ if ( ( year < ( cur_year - 100 ) ) || ( year >= cur_year ) ) {
+ /* you are either tooo old or tooo young to join mxit... sorry */
+ return FALSE;
+ }
+
+ /* special case leap-year */
+ if ( ( year % 4 != 0 ) && ( month == 2 ) && ( day == 29 ) ) {
+ /* cannot have 29 days in February in non leap-years! */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/*------------------------------------------------------------------------
+ * Display the profile information.
+ *
+ * @param session The MXit session object
+ * @param username The username who's profile information this is
+ * @param profile The profile
+ */
+void mxit_show_profile( struct MXitSession* session, const char* username, struct MXitProfile* profile )
+{
+ PurpleNotifyUserInfo* info = purple_notify_user_info_new();
+ struct contact* contact = NULL;
+ PurpleBuddy* buddy;
+
+ buddy = purple_find_buddy( session->acc, username );
+ if ( buddy ) {
+ purple_notify_user_info_add_pair( info, _( "Alias" ), buddy->alias );
+ purple_notify_user_info_add_section_break( info );
+ contact = buddy->proto_data;
+ }
+
+ purple_notify_user_info_add_pair( info, _( "Nick Name" ), profile->nickname );
+ purple_notify_user_info_add_pair( info, _( "Birthday" ), profile->birthday );
+ purple_notify_user_info_add_pair( info, _( "Gender" ), profile->male ? _( "Male" ) : _( "Female" ) );
+ purple_notify_user_info_add_pair( info, _( "Hidden Number" ), profile->hidden ? _( "Yes" ) : _( "No" ) );
+
+ purple_notify_user_info_add_section_break( info );
+
+ /* optional information */
+ purple_notify_user_info_add_pair( info, _( "Title" ), profile->title );
+ purple_notify_user_info_add_pair( info, _( "First Name" ), profile->firstname );
+ purple_notify_user_info_add_pair( info, _( "Last Name" ), profile->lastname );
+ purple_notify_user_info_add_pair( info, _( "Email" ), profile->email );
+
+ purple_notify_user_info_add_section_break( info );
+
+ if ( contact ) {
+ /* presence */
+ purple_notify_user_info_add_pair( info, _( "Status" ), mxit_convert_presence_to_name( contact->presence ) );
+
+ /* mood */
+ if ( contact->mood != MXIT_MOOD_NONE )
+ purple_notify_user_info_add_pair( info, _( "Mood" ), mxit_convert_mood_to_name( contact->mood ) );
+ else
+ purple_notify_user_info_add_pair( info, _( "Mood" ), _( "None" ) );
+
+ /* status message */
+ if ( contact->statusMsg )
+ purple_notify_user_info_add_pair( info, _( "Status Message" ), contact->statusMsg );
+
+ /* subscription type */
+ purple_notify_user_info_add_pair( info, _( "Subscription" ), mxit_convert_subtype_to_name( contact->subtype ) );
+ }
+
+ purple_notify_userinfo( session->con, username, info, NULL, NULL );
+ purple_notify_user_info_destroy( info );
+}
diff --git a/libpurple/protocols/mxit/profile.h b/libpurple/protocols/mxit/profile.h
new file mode 100644
index 0000000000..0e36cc7e83
--- /dev/null
+++ b/libpurple/protocols/mxit/profile.h
@@ -0,0 +1,56 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- user profile's --
+ *
+ * Andrew Victor <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
+ */
+
+#ifndef _MXIT_PROFILE_H_
+#define _MXIT_PROFILE_H_
+
+#include <glib.h>
+
+
+struct MXitProfile {
+ /* required */
+ char loginname[64]; /* name user uses to log into MXit with (aka 'mxitid') */
+ char nickname[64]; /* user's own display name (aka 'nickname', aka 'fullname', aka 'alias') in MXit */
+ char birthday[16]; /* user's birthday "YYYY-MM-DD" */
+ gboolean male; /* true if the user's gender is male (otherwise female) */
+ char pin[16]; /* user's password */
+
+ /* optional */
+ char title[32]; /* user's title */
+ char firstname[64]; /* user's first name */
+ char lastname[64]; /* user's last name (aka 'surname') */
+ char email[64]; /* user's email address */
+ char mobilenr[21]; /* user's mobile number */
+
+ gboolean hidden; /* set if the user's msisdn should remain hidden */
+};
+
+struct MXitSession;
+void mxit_show_profile( struct MXitSession* session, const char* username, struct MXitProfile* profile );
+
+gboolean validateDate( const char* bday );
+
+
+#endif /* _MXIT_PROFILE_H_ */
diff --git a/libpurple/protocols/mxit/protocol.c b/libpurple/protocols/mxit/protocol.c
new file mode 100644
index 0000000000..2978d28b34
--- /dev/null
+++ b/libpurple/protocols/mxit/protocol.c
@@ -0,0 +1,2442 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit client protocol implementation --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "purple.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "roster.h"
+#include "chunk.h"
+#include "filexfer.h"
+#include "markup.h"
+#include "multimx.h"
+#include "splashscreen.h"
+#include "login.h"
+#include "formcmds.h"
+#include "http.h"
+
+
+#define MXIT_MS_OFFSET 3
+
+/* configure the right record terminator char to use */
+#define CP_REC_TERM ( ( session->http ) ? CP_HTTP_REC_TERM : CP_SOCK_REC_TERM )
+
+
+
+/*------------------------------------------------------------------------
+ * Display a notification popup message to the user.
+ *
+ * @param type The type of notification:
+ * - info: PURPLE_NOTIFY_MSG_INFO
+ * - warning: PURPLE_NOTIFY_MSG_WARNING
+ * - error: PURPLE_NOTIFY_MSG_ERROR
+ * @param heading Heading text
+ * @param message Message text
+ */
+void mxit_popup( int type, const char* heading, const char* message )
+{
+ /* (reference: "libpurple/notify.h") */
+ purple_notify_message( NULL, type, _( MXIT_POPUP_WIN_NAME ), heading, message, NULL, NULL );
+}
+
+
+/*------------------------------------------------------------------------
+ * For compatibility with legacy clients, all usernames are sent from MXit with a domain
+ * appended. For MXit contacts, this domain is set to "@m". This function strips
+ * those fake domains.
+ *
+ * @param username The username of the contact
+ */
+void mxit_strip_domain( char* username )
+{
+ if ( g_str_has_suffix( username, "@m" ) )
+ username[ strlen(username) - 2 ] = '\0';
+}
+
+
+/*------------------------------------------------------------------------
+ * Dump a byte buffer to the console for debugging purposes.
+ *
+ * @param buf The data
+ * @param len The data length
+ */
+void dump_bytes( struct MXitSession* session, const char* buf, int len )
+{
+ char msg[( len * 3 ) + 1];
+ int i;
+
+ memset( msg, 0x00, sizeof( msg ) );
+
+ for ( i = 0; i < len; i++ ) {
+ if ( buf[i] == CP_REC_TERM ) /* record terminator */
+ msg[i] = '!';
+ else if ( buf[i] == CP_FLD_TERM ) /* field terminator */
+ msg[i] = '^';
+ else if ( buf[i] == CP_PKT_TERM ) /* packet terminator */
+ msg[i] = '@';
+ else if ( buf[i] < 0x20 )
+ msg[i] = '_';
+ else
+ msg[i] = buf[i];
+
+ }
+
+ purple_debug_info( MXIT_PLUGIN_ID, "DUMP: '%s'\n", msg );
+}
+
+
+/*------------------------------------------------------------------------
+ * Determine if we have an active chat with a specific contact
+ *
+ * @param session The MXit session object
+ * @param who The contact name
+ * @return Return true if we have an active chat with the contact
+ */
+gboolean find_active_chat( const GList* chats, const char* who )
+{
+ const GList* list = chats;
+ const char* chat = NULL;
+
+ while ( list ) {
+ chat = (const char*) list->data;
+
+ if ( strcmp( chat, who ) == 0 )
+ return TRUE;
+
+ list = g_list_next( list );
+ }
+
+ return FALSE;
+}
+
+
+/*========================================================================================================================
+ * Low-level Packet transmission
+ */
+
+/*------------------------------------------------------------------------
+ * Remove next packet from transmission queue.
+ *
+ * @param session The MXit session object
+ * @return The next packet for transmission (or NULL)
+ */
+static struct tx_packet* pop_tx_packet( struct MXitSession* session )
+{
+ struct tx_packet* packet = NULL;
+
+ if ( session->queue.count > 0 ) {
+ /* dequeue the next packet */
+ packet = session->queue.packets[session->queue.rd_i];
+ session->queue.packets[session->queue.rd_i] = NULL;
+ session->queue.rd_i = ( session->queue.rd_i + 1 ) % MAX_QUEUE_SIZE;
+ session->queue.count--;
+ }
+
+ return packet;
+}
+
+
+/*------------------------------------------------------------------------
+ * Add packet to transmission queue.
+ *
+ * @param session The MXit session object
+ * @param packet The packet to transmit
+ * @return Return TRUE if packet was enqueue, or FALSE if queue is full.
+ */
+static gboolean push_tx_packet( struct MXitSession* session, struct tx_packet* packet )
+{
+ if ( session->queue.count < MAX_QUEUE_SIZE ) {
+ /* enqueue packet */
+ session->queue.packets[session->queue.wr_i] = packet;
+ session->queue.wr_i = ( session->queue.wr_i + 1 ) % MAX_QUEUE_SIZE;
+ session->queue.count++;
+ return TRUE;
+ }
+ else
+ return FALSE; /* queue is full */
+}
+
+
+/*------------------------------------------------------------------------
+ * Deallocate transmission packet.
+ *
+ * @param packet The packet to deallocate.
+ */
+static void free_tx_packet( struct tx_packet* packet )
+{
+ g_free( packet->data );
+ g_free( packet );
+ packet = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Flush all the packets from the tx queue and release the resources.
+ *
+ * @param session The MXit session object
+ */
+static void flush_queue( struct MXitSession* session )
+{
+ struct tx_packet* packet;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "flushing the tx queue\n" );
+
+ while ( (packet = pop_tx_packet( session ) ) != NULL )
+ free_tx_packet( packet );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 3: Write the packet data to the TCP connection.
+ *
+ * @param fd The file descriptor
+ * @param pktdata The packet data
+ * @param pktlen The length of the packet data
+ * @return Return -1 on error, otherwise 0
+ */
+static int mxit_write_sock_packet( int fd, const char* pktdata, int pktlen )
+{
+ int written;
+ int res;
+
+ written = 0;
+ while ( written < pktlen ) {
+ res = write( fd, &pktdata[written], pktlen - written );
+ if ( res <= 0 ) {
+ /* error on socket */
+ if ( errno == EAGAIN )
+ continue;
+
+ purple_debug_error( MXIT_PLUGIN_ID, "Error while writing packet to MXit server (%i)\n", res );
+ return -1;
+ }
+ written += res;
+ }
+
+ return 0;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback called for handling a HTTP GET response
+ *
+ * @param url_data libPurple internal object (see purple_util_fetch_url_request)
+ * @param user_data The MXit session object
+ * @param url_text The data returned (could be NULL if error)
+ * @param len The length of the data returned (0 if error)
+ * @param error_message Descriptive error message
+ */
+static void mxit_cb_http_rx( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+
+ /* clear outstanding request */
+ session->http_out_req = NULL;
+
+ if ( ( !url_text ) || ( len == 0 ) ) {
+ /* error with request */
+ purple_debug_error( MXIT_PLUGIN_ID, "HTTP response error (%s)\n", error_message );
+ return;
+ }
+
+ /* convert the HTTP result */
+ memcpy( session->rx_dbuf, url_text, len );
+ session->rx_i = len;
+
+ mxit_parse_packet( session );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 3: Write the packet data to the HTTP connection (GET style).
+ *
+ * @param session The MXit session object
+ * @param pktdata The packet data
+ * @param pktlen The length of the packet data
+ * @return Return -1 on error, otherwise 0
+ */
+static void mxit_write_http_get( struct MXitSession* session, struct tx_packet* packet )
+{
+ char* part = NULL;
+ char* url = NULL;
+
+ if ( packet->datalen > 0 ) {
+ char* tmp = NULL;
+
+ tmp = g_strndup( packet->data, packet->datalen );
+ part = g_strdup( purple_url_encode( tmp ) );
+ g_free( tmp );
+ }
+
+ url = g_strdup_printf( "%s?%s%s", session->http_server, purple_url_encode( packet->header ), ( !part ) ? "" : part );
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "HTTP GET: '%s'\n", url );
+#endif
+
+ /* send the HTTP request */
+ session->http_out_req = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_http_rx, session );
+
+ g_free( url );
+ if ( part )
+ g_free( part );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 3: Write the packet data to the HTTP connection (POST style).
+ *
+ * @param session The MXit session object
+ * @param pktdata The packet data
+ * @param pktlen The length of the packet data
+ * @return Return -1 on error, otherwise 0
+ */
+static void mxit_write_http_post( struct MXitSession* session, struct tx_packet* packet )
+{
+ char request[256 + packet->datalen];
+ int reqlen;
+ char* host_name;
+ int host_port;
+ gboolean ok;
+
+ /* extract the HTTP host name and host port number to connect to */
+ ok = purple_url_parse( session->http_server, &host_name, &host_port, NULL, NULL, NULL );
+ if ( !ok ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "HTTP POST error: (host name '%s' not valid)\n", session->http_server );
+ }
+
+ /* strip off the last '&' from the header */
+ packet->header[packet->headerlen - 1] = '\0';
+ packet->headerlen--;
+
+ /* build the HTTP request packet */
+ reqlen = g_snprintf( request, 256,
+ "POST %s?%s HTTP/1.1\r\n"
+ "User-Agent: " MXIT_HTTP_USERAGENT "\r\n"
+ "Content-Type: application/octet-stream\r\n"
+ "Host: %s\r\n"
+ "Content-Length: %" G_GSIZE_FORMAT "\r\n"
+ "\r\n",
+ session->http_server,
+ purple_url_encode( packet->header ),
+ host_name,
+ packet->datalen - MXIT_MS_OFFSET
+ );
+
+ /* copy over the packet body data (could be binary) */
+ memcpy( request + reqlen, packet->data + MXIT_MS_OFFSET, packet->datalen - MXIT_MS_OFFSET );
+ reqlen += packet->datalen;
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST:\n" );
+ dump_bytes( session, request, reqlen );
+#endif
+
+ /* send the request to the HTTP server */
+ mxit_http_send_request( session, host_name, host_port, request, reqlen );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 2: Handle the transmission of the packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param packet The packet to transmit
+ */
+static void mxit_send_packet( struct MXitSession* session, struct tx_packet* packet )
+{
+ int res;
+
+ if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) {
+ /* we are not connected so ignore all packets to be send */
+ purple_debug_error( MXIT_PLUGIN_ID, "Dropping TX packet (we are not connected)\n" );
+ return;
+ }
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Packet send CMD:%i (%i)\n", packet->cmd, packet->headerlen + packet->datalen );
+#ifdef DEBUG_PROTOCOL
+ dump_bytes( session, packet->header, packet->headerlen );
+ dump_bytes( session, packet->data, packet->datalen );
+#endif
+
+ if ( !session->http ) {
+ /* socket connection */
+ char data[packet->datalen + packet->headerlen];
+ int datalen;
+
+ /* create raw data buffer */
+ memcpy( data, packet->header, packet->headerlen );
+ memcpy( data + packet->headerlen, packet->data, packet->datalen );
+ datalen = packet->headerlen + packet->datalen;
+
+ res = mxit_write_sock_packet( session->fd, data, datalen );
+ if ( res < 0 ) {
+ /* we must have lost the connection, so terminate it so that we can reconnect */
+ purple_connection_error( session->con, _( "We have lost the connection to MXit. Please reconnect." ) );
+ }
+ }
+ else {
+ /* http connection */
+
+ if ( packet->cmd == CP_CMD_MEDIA ) {
+ /* multimedia packets must be send with a HTTP POST */
+ mxit_write_http_post( session, packet );
+ }
+ else {
+ mxit_write_http_get( session, packet );
+ }
+ }
+
+ /* update the timestamp of the last-transmitted packet */
+ session->last_tx = time( NULL );
+
+ /*
+ * we need to remember that we are still waiting for the ACK from
+ * the server on this request
+ */
+ session->outack = packet->cmd;
+
+ /* free up the packet resources */
+ free_tx_packet( packet );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 1: Create a new Tx packet and queue it for sending.
+ *
+ * @param session The MXit session object
+ * @param data The packet data (payload)
+ * @param datalen The length of the packet data
+ * @param cmd The MXit command for this packet
+ */
+static void mxit_queue_packet( struct MXitSession* session, const char* data, int datalen, int cmd )
+{
+ struct tx_packet* packet;
+ char header[256];
+ int hlen;
+
+ /* create a packet for sending */
+ packet = g_new0( struct tx_packet, 1 );
+ packet->data = g_malloc0( datalen );
+ packet->cmd = cmd;
+ packet->headerlen = 0;
+
+ /* create generic packet header */
+ hlen = sprintf( header, "id=%s%c", session->acc->username, CP_REC_TERM ); /* client msisdn */
+
+ if ( session->http ) {
+ /* http connection only */
+ hlen += sprintf( header + hlen, "s=" );
+ if ( session->http_sesid > 0 ) {
+ hlen += sprintf( header + hlen, "%u%c", session->http_sesid, CP_FLD_TERM ); /* http session id */
+ }
+ session->http_seqno++;
+ hlen += sprintf( header + hlen, "%u%c", session->http_seqno, CP_REC_TERM ); /* http request sequence id */
+ }
+
+ hlen += sprintf( header + hlen, "cm=%i%c", cmd, CP_REC_TERM ); /* packet command */
+
+ if ( !session->http ) {
+ /* socket connection only */
+ packet->headerlen += sprintf( packet->header, "ln=%i%c", ( datalen + hlen ), CP_REC_TERM ); /* packet length */
+ }
+
+ /* copy the header to packet */
+ memcpy( packet->header + packet->headerlen, header, hlen );
+ packet->headerlen += hlen;
+
+ /* copy payload to packet */
+ if ( datalen > 0 )
+ memcpy( packet->data, data, datalen );
+ packet->datalen = datalen;
+
+
+ /*
+ * shortcut: first check if there are any commands still outstanding.
+ * if not, then we might as well just write this packet directly and
+ * skip the whole queueing thing
+ */
+ if ( session->outack == 0 ) {
+ /* no outstanding ACKs, so we might as well write it directly */
+ mxit_send_packet( session, packet );
+ }
+ else {
+ /* ACK still outstanding, so we need to queue this request until we have the ACK */
+
+ if ( ( packet->cmd == CP_CMD_PING ) || ( packet->cmd == CP_CMD_POLL ) ) {
+ /* we do NOT queue HTTP poll nor socket ping packets */
+ free_tx_packet( packet );
+ return;
+ }
+
+ purple_debug_info( MXIT_PLUGIN_ID, "queueing packet for later sending cmd=%i\n", cmd );
+ if ( !push_tx_packet( session, packet ) ) {
+ /* packet could not be queued for transmission */
+ mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Message Send Error" ), _( "Unable to process your request at this time" ) );
+ free_tx_packet( packet );
+ }
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback to manage the packet send queue (send next packet, timeout's, etc).
+ *
+ * @param session The MXit session object
+ */
+gboolean mxit_manage_queue( gpointer user_data )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+ struct tx_packet* packet = NULL;
+
+ if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) {
+ /* we are not connected, so ignore the queue */
+ return TRUE;
+ }
+ else if ( session->outack > 0 ) {
+ /* we are still waiting for an outstanding ACK from the MXit server */
+ if ( session->last_tx <= time( NULL ) - MXIT_ACK_TIMEOUT ) {
+ /* ack timeout! so we close the connection here */
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_manage_queue: Timeout awaiting ACK for command '%X'\n", session->outack );
+ purple_connection_error( session->con, _( "Timeout while waiting for a response from the MXit server." ) );
+ }
+ return TRUE;
+ }
+
+ packet = pop_tx_packet( session );
+ if ( packet != NULL ) {
+ /* there was a packet waiting to be sent to the server, now is the time to do something about it */
+
+ /* send the packet to MXit server */
+ mxit_send_packet( session, packet );
+ }
+
+ return TRUE;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback to manage HTTP server polling (HTTP connections ONLY)
+ *
+ * @param session The MXit session object
+ */
+gboolean mxit_manage_polling( gpointer user_data )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+ gboolean poll = FALSE;
+ time_t now = time( NULL );
+ int polldiff;
+ int rxdiff;
+
+ if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) {
+ /* we only poll if we are actually logged in */
+ return TRUE;
+ }
+
+ /* calculate the time differences */
+ rxdiff = now - session->last_rx;
+ polldiff = now - session->http_last_poll;
+
+ if ( rxdiff < MXIT_HTTP_POLL_MIN ) {
+ /* we received some reply a few moments ago, so reset the poll interval */
+ session->http_interval = MXIT_HTTP_POLL_MIN;
+ }
+ else if ( session->http_last_poll < ( now - session->http_interval ) ) {
+ /* time to poll again */
+ poll = TRUE;
+
+ /* back-off some more with the polling */
+ session->http_interval = session->http_interval + ( session->http_interval / 2 );
+ if ( session->http_interval > MXIT_HTTP_POLL_MAX )
+ session->http_interval = MXIT_HTTP_POLL_MAX;
+ }
+
+ /* debugging */
+ //purple_debug_info( MXIT_PLUGIN_ID, "POLL TIMER: %i (%i,%i)\n", session->http_interval, rxdiff, polldiff );
+
+ if ( poll ) {
+ /* send poll request */
+ session->http_last_poll = time( NULL );
+ mxit_send_poll( session );
+ }
+
+ return TRUE;
+}
+
+
+/*========================================================================================================================
+ * Send MXit operations.
+ */
+
+/*------------------------------------------------------------------------
+ * Send a ping/keepalive packet to MXit server.
+ *
+ * @param session The MXit session object
+ */
+void mxit_send_ping( struct MXitSession* session )
+{
+ /* queue packet for transmission */
+ mxit_queue_packet( session, NULL, 0, CP_CMD_PING );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a poll request to the HTTP server (HTTP connections ONLY).
+ *
+ * @param session The MXit session object
+ */
+void mxit_send_poll( struct MXitSession* session )
+{
+ /* queue packet for transmission */
+ mxit_queue_packet( session, NULL, 0, CP_CMD_POLL );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a logout packet to the MXit server.
+ *
+ * @param session The MXit session object
+ */
+void mxit_send_logout( struct MXitSession* session )
+{
+ /* queue packet for transmission */
+ mxit_queue_packet( session, NULL, 0, CP_CMD_LOGOUT );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a register packet to the MXit server.
+ *
+ * @param session The MXit session object
+ */
+void mxit_send_register( struct MXitSession* session )
+{
+ struct MXitProfile* profile = session->profile;
+ const char* locale;
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%s%c%i%c%s%c" /* "ms"=password\1version\1maxreplyLen\1name\1 */
+ "%s%c%i%c%s%c%s%c" /* dateOfBirth\1gender\1location\1capabilities\1 */
+ "%s%c%i%c%s%c%s", /* dc\1features\1dialingcode\1locale */
+ session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, CP_MAX_FILESIZE, CP_FLD_TERM, profile->nickname, CP_FLD_TERM,
+ profile->birthday, CP_FLD_TERM, ( profile->male ) ? 1 : 0, CP_FLD_TERM, MXIT_DEFAULT_LOC, CP_FLD_TERM, MXIT_CP_CAP, CP_FLD_TERM,
+ session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM, session->dialcode, CP_FLD_TERM, locale
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_REGISTER );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a login packet to the MXit server.
+ *
+ * @param session The MXit session object
+ */
+void mxit_send_login( struct MXitSession* session )
+{
+ const char* splashId;
+ const char* locale;
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%s%c%i%c" /* "ms"=password\1version\1getContacts\1 */
+ "%s%c%s%c%i%c" /* capabilities\1dc\1features\1 */
+ "%s%c%s", /* dialingcode\1locale */
+ session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, 1, CP_FLD_TERM,
+ MXIT_CP_CAP, CP_FLD_TERM, session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM,
+ session->dialcode, CP_FLD_TERM, locale
+ );
+
+ /* include "custom resource" information */
+ splashId = splash_current( session );
+ if ( splashId != NULL )
+ datalen += sprintf( data + datalen, "%ccr=%s", CP_REC_TERM, splashId );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_LOGIN );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a chat message packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param to The username of the recipient
+ * @param msg The message text
+ */
+void mxit_send_message( struct MXitSession* session, const char* to, const char* msg, gboolean parse_markup )
+{
+ char data[CP_MAX_PACKET];
+ char* markuped_msg;
+ int datalen;
+ int msgtype = CP_MSGTYPE_NORMAL;
+
+ /* first we need to convert the markup from libPurple to MXit format */
+ if ( parse_markup )
+ markuped_msg = mxit_convert_markup_tx( msg, &msgtype );
+ else
+ markuped_msg = g_strdup( msg );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%s%c%i%c%i", /* "ms"=jid\1msg\1type\1flags */
+ to, CP_FLD_TERM, markuped_msg, CP_FLD_TERM, msgtype, CP_FLD_TERM, CP_MSG_MARKUP | CP_MSG_EMOTICON
+ );
+
+ /* free the resources */
+ g_free( markuped_msg );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_TX_MSG );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a extended profile request packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param username Username who's profile is being requested (NULL = our own)
+ * @param nr_attribs Number of attributes being requested
+ * @param attributes The names of the attributes
+ */
+void mxit_send_extprofile_request( struct MXitSession* session, const char* username, unsigned int nr_attrib, const char* attribute[] )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+ unsigned int i;
+
+ datalen = sprintf( data, "ms=%s%c%i", /* "ms="mxitid\1nr_attributes */
+ (username ? username : ""), CP_FLD_TERM, nr_attrib);
+
+ /* add attributes */
+ for ( i = 0; i < nr_attrib; i++ )
+ datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, attribute[i] );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_GET );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an update profile packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param password The new password to be used for logging in (optional)
+ * @param nr_attrib The number of attributes
+ * @param attributes String containing the attributes and settings seperated by '0x01'
+ */
+void mxit_send_extprofile_update( struct MXitSession* session, const char* password, unsigned int nr_attrib, const char* attributes )
+{
+ char data[CP_MAX_PACKET];
+ gchar** parts;
+ int datalen;
+ unsigned int i;
+
+ parts = g_strsplit( attributes, "\01", ( MXIT_MAX_ATTRIBS * 3 ) );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%i", /* "ms"=password\1nr_attibutes */
+ ( password ) ? password : "", CP_FLD_TERM, nr_attrib
+ );
+
+ /* add attributes */
+ for ( i = 1; i < nr_attrib * 3; i+=3 )
+ datalen += sprintf( data + datalen, "%c%s%c%s%c%s", /* \1name\1type\1value */
+ CP_FLD_TERM, parts[i], CP_FLD_TERM, parts[i + 1], CP_FLD_TERM, parts[i + 2] );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_SET );
+
+ /* freeup the memory */
+ g_strfreev( parts );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a presence update packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param presence The presence (as per MXit types)
+ * @param statusmsg The status message (can be NULL)
+ */
+void mxit_send_presence( struct MXitSession* session, int presence, const char* statusmsg )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%i%c", /* "ms"=show\1status */
+ presence, CP_FLD_TERM
+ );
+
+ /* append status message (if one is set) */
+ if ( statusmsg )
+ datalen += sprintf( data + datalen, "%s", statusmsg );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_STATUS );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a mood update packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param mood The mood (as per MXit types)
+ */
+void mxit_send_mood( struct MXitSession* session, int mood )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%i", /* "ms"=mood */
+ mood
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_MOOD );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an invite contact packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param username The username of the contact being invited
+ * @param alias Our alias for the contact
+ * @param groupname Group in which contact should be stored.
+ */
+void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%s%c%s%c%i%c%s", /* "ms"=group\1username\1alias\1type\1msg */
+ groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias,
+ CP_FLD_TERM, MXIT_TYPE_MXIT, CP_FLD_TERM, ""
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_INVITE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a remove contact packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param username The username of the contact being removed
+ */
+void mxit_send_remove( struct MXitSession* session, const char* username )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s", /* "ms"=username */
+ username
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_REMOVE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an accept subscription (invite) packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param username The username of the contact being accepted
+ * @param alias Our alias for the contact
+ */
+void mxit_send_allow_sub( struct MXitSession* session, const char* username, const char* alias )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%s%c%s", /* "ms"=username\1group\1alias */
+ username, CP_FLD_TERM, "", CP_FLD_TERM, alias
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_ALLOW );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an deny subscription (invite) packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param username The username of the contact being denied
+ */
+void mxit_send_deny_sub( struct MXitSession* session, const char* username )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s", /* "ms"=username */
+ username
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_DENY );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an update contact packet to the MXit server.
+ *
+ * @param session The MXit session object
+ * @param username The username of the contact being denied
+ * @param alias Our alias for the contact
+ * @param groupname Group in which contact should be stored.
+ */
+void mxit_send_update_contact( struct MXitSession* session, const char* username, const char* alias, const char* groupname )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%s%c%s", /* "ms"=groupname\1username\1alias */
+ groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_UPDATE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a splash-screen click event packet.
+ *
+ * @param session The MXit session object
+ * @param splashid The identifier of the splash-screen
+ */
+void mxit_send_splashclick( struct MXitSession* session, const char* splashid )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s", /* "ms"=splashId */
+ splashid
+ );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_SPLASHCLICK );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send packet to create a MultiMX room.
+ *
+ * @param session The MXit session object
+ * @param groupname Name of the room to create
+ * @param nr_usernames Number of users in initial invite
+ * @param usernames The usernames of the users in the initial invite
+ */
+void mxit_send_groupchat_create( struct MXitSession* session, const char* groupname, int nr_usernames, const char* usernames[] )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+ int i;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%i", /* "ms"=roomname\1nr_jids\1jid0\1..\1jidN */
+ groupname, CP_FLD_TERM, nr_usernames
+ );
+
+ /* add usernames */
+ for ( i = 0; i < nr_usernames; i++ )
+ datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, usernames[i] );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_CREATE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send packet to invite users to existing MultiMX room.
+ *
+ * @param session The MXit session object
+ * @param roomid The unique RoomID for the MultiMx room.
+ * @param nr_usernames Number of users being invited
+ * @param usernames The usernames of the users being invited
+ */
+
+void mxit_send_groupchat_invite( struct MXitSession* session, const char* roomid, int nr_usernames, const char* usernames[] )
+{
+ char data[CP_MAX_PACKET];
+ int datalen;
+ int i;
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=%s%c%i", /* "ms"=roomid\1nr_jids\1jid0\1..\1jidN */
+ roomid, CP_FLD_TERM, nr_usernames
+ );
+
+ /* add usernames */
+ for ( i = 0; i < nr_usernames; i++ )
+ datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, usernames[i] );
+
+ /* queue packet for transmission */
+ mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_INVITE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "send file direct" multimedia packet.
+ *
+ * @param session The MXit session object
+ * @param username The username of the recipient
+ * @param filename The name of the file being sent
+ * @param buf The content of the file
+ * @param buflen The length of the file contents
+ */
+void mxit_send_file( struct MXitSession* session, const char* username, const char* filename, const unsigned char* buf, int buflen )
+{
+ char data[CP_MAX_PACKET];
+ int datalen = 0;
+ struct raw_chunk* chunk;
+ int size;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "SENDING FILE '%s' of %i bytes to user '%s'\n", filename, buflen, username );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=" );
+
+ /* map chunk header over data buffer */
+ chunk = (struct raw_chunk *) &data[datalen];
+
+ size = mxit_chunk_create_senddirect( chunk->data, username, filename, buf, buflen );
+ if ( size < 0 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Error creating senddirect chunk (%i)\n", size );
+ return;
+ }
+
+ chunk->type = CP_CHUNK_DIRECT_SND;
+ chunk->length = htonl( size );
+ datalen += sizeof( struct raw_chunk ) + size;
+
+ /* send the byte stream to the mxit server */
+ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "reject file" multimedia packet.
+ *
+ * @param session The MXit session object
+ * @param fileid A unique ID that identifies this file
+ */
+void mxit_send_file_reject( struct MXitSession* session, const char* fileid )
+{
+ char data[CP_MAX_PACKET];
+ int datalen = 0;
+ struct raw_chunk* chunk;
+ int size;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_reject\n" );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=" );
+
+ /* map chunk header over data buffer */
+ chunk = (struct raw_chunk *) &data[datalen];
+
+ size = mxit_chunk_create_reject( chunk->data, fileid );
+ if ( size < 0 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Error creating reject chunk (%i)\n", size );
+ return;
+ }
+
+ chunk->type = CP_CHUNK_REJECT;
+ chunk->length = htonl( size );
+ datalen += sizeof( struct raw_chunk ) + size;
+
+ /* send the byte stream to the mxit server */
+ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "get file" multimedia packet.
+ *
+ * @param session The MXit session object
+ * @param fileid A unique ID that identifies this file
+ * @param filesize The number of bytes to retrieve
+ * @param offset Offset in file at which to start retrieving
+ */
+void mxit_send_file_accept( struct MXitSession* session, const char* fileid, int filesize, int offset )
+{
+ char data[CP_MAX_PACKET];
+ int datalen = 0;
+ struct raw_chunk* chunk;
+ int size;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_accept\n" );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=" );
+
+ /* map chunk header over data buffer */
+ chunk = (struct raw_chunk *) &data[datalen];
+
+ size = mxit_chunk_create_get( chunk->data, fileid, filesize, offset );
+ if ( size < 0 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Error creating getfile chunk (%i)\n", size );
+ return;
+ }
+
+ chunk->type = CP_CHUNK_GET;
+ chunk->length = htonl( size );
+ datalen += sizeof( struct raw_chunk ) + size;
+
+ /* send the byte stream to the mxit server */
+ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "received file" multimedia packet.
+ *
+ * @param session The MXit session object
+ * @param status The status of the file-transfer
+ */
+void mxit_send_file_received( struct MXitSession* session, const char* fileid, short status )
+{
+ char data[CP_MAX_PACKET];
+ int datalen = 0;
+ struct raw_chunk* chunk;
+ int size;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_received\n" );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=" );
+
+ /* map chunk header over data buffer */
+ chunk = (struct raw_chunk *) &data[datalen];
+
+ size = mxit_chunk_create_received( chunk->data, fileid, status );
+ if ( size < 0 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Error creating received chunk (%i)\n", size );
+ return;
+ }
+
+ chunk->type = CP_CHUNK_RECIEVED;
+ chunk->length = htonl( size );
+ datalen += sizeof( struct raw_chunk ) + size;
+
+ /* send the byte stream to the mxit server */
+ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "set avatar" multimedia packet.
+ *
+ * @param session The MXit session object
+ * @param data The avatar data
+ * @param buflen The length of the avatar data
+ */
+void mxit_set_avatar( struct MXitSession* session, const unsigned char* avatar, int avatarlen )
+{
+ char data[CP_MAX_PACKET];
+ int datalen = 0;
+ struct raw_chunk* chunk;
+ int size;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_set_avatar: %i bytes\n", avatarlen );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=" );
+
+ /* map chunk header over data buffer */
+ chunk = (struct raw_chunk *) &data[datalen];
+
+ size = mxit_chunk_create_set_avatar( chunk->data, avatar, avatarlen );
+ if ( size < 0 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Error creating set avatar chunk (%i)\n", size );
+ return;
+ }
+
+ chunk->type = CP_CHUNK_SET_AVATAR;
+ chunk->length = htonl( size );
+ datalen += sizeof( struct raw_chunk ) + size;
+
+ /* send the byte stream to the mxit server */
+ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "get avatar" multimedia packet.
+ *
+ * @param session The MXit session object
+ * @param mxitId The username who's avatar to request
+ * @param avatarId The id of the avatar image (as string)
+ * @param data The avatar data
+ * @param buflen The length of the avatar data
+ */
+void mxit_get_avatar( struct MXitSession* session, const char* mxitId, const char* avatarId )
+{
+ char data[CP_MAX_PACKET];
+ int datalen = 0;
+ struct raw_chunk* chunk;
+ int size;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_get_avatar: %s\n", mxitId );
+
+ /* convert the packet to a byte stream */
+ datalen = sprintf( data, "ms=" );
+
+ /* map chunk header over data buffer */
+ chunk = (struct raw_chunk *) &data[datalen];
+
+ size = mxit_chunk_create_get_avatar( chunk->data, mxitId, avatarId, MXIT_AVATAR_SIZE );
+ if ( size < 0 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "Error creating get avatar chunk (%i)\n", size );
+ return;
+ }
+
+ chunk->type = CP_CHUNK_GET_AVATAR;
+ chunk->length = htonl( size );
+ datalen += sizeof( struct raw_chunk ) + size;
+
+ /* send the byte stream to the mxit server */
+ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a login message packet.
+ *
+ * @param session The MXit session object
+ * @param records The packet's data records
+ * @param rcount The number of data records
+ */
+static void mxit_parse_cmd_login( struct MXitSession* session, struct record** records, int rcount )
+{
+ PurpleStatus* status;
+ int presence;
+ const char* profilelist[] = { CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_HIDENUMBER, CP_PROFILE_FULLNAME,
+ CP_PROFILE_TITLE, CP_PROFILE_FIRSTNAME, CP_PROFILE_LASTNAME, CP_PROFILE_EMAIL,
+ CP_PROFILE_MOBILENR };
+
+ purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
+
+ /* we were not yet logged in so we need to complete the login sequence here */
+ session->flags |= MXIT_FLAG_LOGGEDIN;
+ purple_connection_update_progress( session->con, _( "Successfully Logged In..." ), 3, 4 );
+ purple_connection_set_state( session->con, PURPLE_CONNECTED );
+
+ /* display the current splash-screen */
+ if ( splash_popup_enabled( session ) )
+ splash_display( session );
+
+ /* update presence status */
+ status = purple_account_get_active_status( session->acc );
+ presence = mxit_convert_presence( purple_status_get_id( status ) );
+ if ( presence != MXIT_PRESENCE_ONLINE ) {
+ /* when logging into MXit, your default presence is online. but with the UI, one can change
+ * the presence to whatever. in the case where its changed to a different presence setting
+ * we need to send an update to the server, otherwise the user's presence will be out of
+ * sync between the UI and MXit.
+ */
+ mxit_send_presence( session, presence, purple_status_get_attr_string( status, "message" ) );
+ }
+
+ /* save extra info if this is a HTTP connection */
+ if ( session->http ) {
+ /* save the http server to use for this session */
+ g_strlcpy( session->http_server, records[1]->fields[3]->data, sizeof( session->http_server ) );
+
+ /* save the session id */
+ session->http_sesid = atoi( records[0]->fields[0]->data );
+ }
+
+ /* retrieve our MXit profile */
+ mxit_send_extprofile_request( session, NULL, ARRAY_SIZE( profilelist ), profilelist );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received message packet.
+ *
+ * @param session The MXit session object
+ * @param records The packet's data records
+ * @param rcount The number of data records
+ */
+static void mxit_parse_cmd_message( struct MXitSession* session, struct record** records, int rcount )
+{
+ struct RXMsgData* mx = NULL;
+ char* message = NULL;
+ int msglen = 0;
+ int msgflags = 0;
+ int msgtype = 0;
+
+ if ( ( rcount == 1 ) || ( records[0]->fcount < 2 ) || ( records[1]->fcount == 0 ) || ( records[1]->fields[0]->len == 0 ) ) {
+ /* packet contains no message or an empty message */
+ return;
+ }
+
+ message = records[1]->fields[0]->data;
+ msglen = strlen( message );
+
+ /* strip off dummy domain */
+ mxit_strip_domain( records[0]->fields[0]->data );
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "Message received from '%s'\n", records[0]->fields[0]->data );
+#endif
+
+ /* decode message flags (if any) */
+ if ( records[0]->fcount >= 5 )
+ msgflags = atoi( records[0]->fields[4]->data );
+ msgtype = atoi( records[0]->fields[2]->data );
+
+ if ( msgflags & CP_MSG_ENCRYPTED ) {
+ /* this is an encrypted message. we do not currently support those so ignore it */
+ PurpleBuddy* buddy;
+ const char* name;
+ char msg[128];
+
+ buddy = purple_find_buddy( session->acc, records[0]->fields[0]->data );
+ if ( buddy )
+ name = purple_buddy_get_alias( buddy );
+ else
+ name = records[0]->fields[0]->data;
+ g_snprintf( msg, sizeof( msg ), "%s sent you an encrypted message, but it is not supported on this client.", name );
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), _( msg ) );
+ return;
+ }
+
+ /* create and initialise new markup struct */
+ mx = g_new0( struct RXMsgData, 1 );
+ mx->msg = g_string_sized_new( msglen );
+ mx->session = session;
+ mx->from = g_strdup( records[0]->fields[0]->data );
+ mx->timestamp = atoi( records[0]->fields[1]->data );
+ mx->got_img = FALSE;
+ mx->chatid = -1;
+ mx->img_count = 0;
+
+ /* update list of active chats */
+ if ( !find_active_chat( session->active_chats, mx->from ) ) {
+ session->active_chats = g_list_append( session->active_chats, g_strdup( mx->from ) );
+ }
+
+ if ( is_multimx_contact( session, mx->from ) ) {
+ /* this is a MultiMx chatroom message */
+ multimx_message_received( mx, message, msglen, msgtype, msgflags );
+ }
+ else {
+ mxit_parse_markup( mx, message, msglen, msgtype, msgflags );
+ }
+
+ /* we are now done parsing the message */
+ mx->converted = TRUE;
+ if ( mx->img_count == 0 ) {
+ /* we have all the data we need for this message to be displayed now. */
+ mxit_show_message( mx );
+ }
+ else {
+ /* this means there are still images outstanding for this message and
+ * still need to wait for them before we can display the message.
+ * so the image received callback function will eventually display
+ * the message. */
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received subscription request packet.
+ *
+ * @param session The MXit session object
+ * @param records The packet's data records
+ * @param rcount The number of data records
+ */
+static void mxit_parse_cmd_new_sub( struct MXitSession* session, struct record** records, int rcount )
+{
+ struct contact* contact;
+ struct record* rec;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_new_sub (%i recs)\n", rcount );
+
+ for ( i = 0; i < rcount; i++ ) {
+ rec = records[i];
+
+ if ( rec->fcount < 4 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "BAD SUBSCRIPTION RECORD! %i fields\n", rec->fcount );
+ break;
+ }
+
+ /* build up a new contact info struct */
+ contact = g_new0( struct contact, 1 );
+
+ strcpy( contact->username, rec->fields[0]->data );
+ mxit_strip_domain( contact->username ); /* remove dummy domain */
+ strcpy( contact->alias, rec->fields[1]->data );
+ contact->type = atoi( rec->fields[2]->data );
+
+ if ( rec->fcount >= 5 ) {
+ /* there is a personal invite message attached */
+ contact->msg = strdup( rec->fields[4]->data );
+ }
+ else
+ contact->msg = NULL;
+
+ /* handle the subscription */
+ if ( contact-> type == MXIT_TYPE_MULTIMX ) { /* subscription to a MultiMX room */
+ char* creator = NULL;
+
+ if ( rec->fcount >= 6 )
+ creator = rec->fields[5]->data;
+
+ multimx_invite( session, contact, creator );
+ }
+ else
+ mxit_new_subscription( session, contact );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received contact update packet.
+ *
+ * @param session The MXit session object
+ * @param records The packet's data records
+ * @param rcount The number of data records
+ */
+static void mxit_parse_cmd_contact( struct MXitSession* session, struct record** records, int rcount )
+{
+ struct contact* contact = NULL;
+ struct record* rec;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_contact (%i recs)\n", rcount );
+
+ for ( i = 0; i < rcount; i++ ) {
+ rec = records[i];
+
+ if ( rec->fcount < 6 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "BAD CONTACT RECORD! %i fields\n", rec->fcount );
+ break;
+ }
+
+ /* build up a new contact info struct */
+ contact = g_new0( struct contact, 1 );
+
+ strcpy( contact->groupname, rec->fields[0]->data );
+ strcpy( contact->username, rec->fields[1]->data );
+ mxit_strip_domain( contact->username ); /* remove dummy domain */
+ strcpy( contact->alias, rec->fields[2]->data );
+
+ contact->presence = atoi( rec->fields[3]->data );
+ contact->type = atoi( rec->fields[4]->data );
+ contact->mood = atoi( rec->fields[5]->data );
+
+ if ( rec->fcount > 6 ) {
+ /* added in protocol 5.9.0 - flags & subtype */
+ contact->flags = atoi( rec->fields[6]->data );
+ contact->subtype = rec->fields[7]->data[0];
+ }
+
+ /* add the contact to the buddy list */
+ if ( contact-> type == MXIT_TYPE_MULTIMX ) /* contact is a MultiMX room */
+ multimx_created( session, contact );
+ else
+ mxit_update_contact( session, contact );
+ }
+
+ if ( !( session->flags & MXIT_FLAG_FIRSTROSTER ) ) {
+ session->flags |= MXIT_FLAG_FIRSTROSTER;
+ mxit_update_blist( session );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received presence update packet.
+ *
+ * @param session The MXit session object
+ * @param records The packet's data records
+ * @param rcount The number of data records
+ */
+static void mxit_parse_cmd_presence( struct MXitSession* session, struct record** records, int rcount )
+{
+ struct record* rec;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_presence (%i recs)\n", rcount );
+
+ for ( i = 0; i < rcount; i++ ) {
+ rec = records[i];
+
+ if ( rec->fcount < 6 ) {
+ purple_debug_error( MXIT_PLUGIN_ID, "BAD PRESENCE RECORD! %i fields\n", rec->fcount );
+ break;
+ }
+
+ /*
+ * The format of the record is:
+ * contactAddressN\1presenceN\1\moodN\1customMoodN\1statusMsgN\1avatarIdN
+ */
+ mxit_strip_domain( rec->fields[0]->data ); /* contactAddress */
+
+ mxit_update_buddy_presence( session, rec->fields[0]->data, atoi( rec->fields[1]->data ), atoi( rec->fields[2]->data ),
+ rec->fields[3]->data, rec->fields[4]->data, rec->fields[5]->data );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received extended profile packet.
+ *
+ * @param session The MXit session object
+ * @param records The packet's data records
+ * @param rcount The number of data records
+ */
+static void mxit_parse_cmd_extprofile( struct MXitSession* session, struct record** records, int rcount )
+{
+ const char* mxitId = records[0]->fields[0]->data;
+ struct MXitProfile* profile = NULL;
+ int count;
+ int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_extprofile: profile for '%s'\n", mxitId );
+
+ profile = g_new0( struct MXitProfile, 1 );
+
+ /* set the count for attributes */
+ count = atoi( records[0]->fields[1]->data );
+
+ for ( i = 0; i < count; i++ ) {
+ char* fname;
+ char* fvalue;
+ char* fstatus;
+ int f = ( i * 3 ) + 2;
+
+ fname = records[0]->fields[f]->data; /* field name */
+ fvalue = records[0]->fields[f + 1]->data; /* field value */
+ fstatus = records[0]->fields[f + 2]->data; /* field status */
+
+ /* first check the status on the returned attribute */
+ if ( fstatus[0] != '0' ) {
+ /* error: attribute requested was NOT found */
+ purple_debug_error( MXIT_PLUGIN_ID, "Bad profile status on attribute '%s' \n", fname );
+ continue;
+ }
+
+ if ( strcmp( CP_PROFILE_BIRTHDATE, fname ) == 0 ) {
+ /* birthdate */
+ if ( records[0]->fields[f + 1]->len > 10 ) {
+ fvalue[10] = '\0';
+ records[0]->fields[f + 1]->len = 10;
+ }
+ memcpy( profile->birthday, fvalue, records[0]->fields[f + 1]->len );
+ }
+ else if ( strcmp( CP_PROFILE_GENDER, fname ) == 0 ) {
+ /* gender */
+ profile->male = ( fvalue[0] == '1' );
+ }
+ else if ( strcmp( CP_PROFILE_HIDENUMBER, fname ) == 0 ) {
+ /* hide number */
+ profile->hidden = ( fvalue[0] == '1' );
+ }
+ else if ( strcmp( CP_PROFILE_FULLNAME, fname ) == 0 ) {
+ /* nickname */
+ g_strlcpy( profile->nickname, fvalue, sizeof( profile->nickname ) );
+ }
+ else if ( strcmp( CP_PROFILE_AVATAR, fname ) == 0 ) {
+ /* avatar id, we just ingore it cause we dont need it */
+ }
+ else if ( strcmp( CP_PROFILE_TITLE, fname ) == 0 ) {
+ /* title */
+ g_strlcpy( profile->title, fvalue, sizeof( profile->title ) );
+ }
+ else if ( strcmp( CP_PROFILE_FIRSTNAME, fname ) == 0 ) {
+ /* first name */
+ g_strlcpy( profile->firstname, fvalue, sizeof( profile->firstname ) );
+ }
+ else if ( strcmp( CP_PROFILE_LASTNAME, fname ) == 0 ) {
+ /* last name */
+ g_strlcpy( profile->lastname, fvalue, sizeof( profile->lastname ) );
+ }
+ else if ( strcmp( CP_PROFILE_EMAIL, fname ) == 0 ) {
+ /* email address */
+ g_strlcpy( profile->email, fvalue, sizeof( profile->email ) );
+ }
+ else if ( strcmp( CP_PROFILE_MOBILENR, fname ) == 0 ) {
+ /* mobile number */
+ g_strlcpy( profile->mobilenr, fvalue, sizeof( profile->mobilenr ) );
+ }
+ else {
+ /* invalid profile attribute */
+ purple_debug_error( MXIT_PLUGIN_ID, "Invalid profile attribute received '%s' \n", fname );
+ }
+ }
+
+ if ( records[0]->fields[0]->len == 0 ) {
+ /* no MXit id provided, so this must be our own profile information */
+ if ( session->profile )
+ g_free( session->profile );
+ session->profile = profile;
+ }
+ else {
+ /* display other user's profile */
+ mxit_show_profile( session, mxitId, profile );
+
+ /* cleanup */
+ g_free( profile );
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the length of a multimedia chunk
+ *
+ * @return The actual chunk data length in bytes
+ */
+static int get_chunk_len( const char* chunkdata )
+{
+ int* sizeptr;
+
+ sizeptr = (int*) &chunkdata[1]; /* we skip the first byte (type field) */
+
+ return ntohl( *sizeptr );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received multimedia packet.
+ *
+ * @param session The MXit session object
+ * @param records The packet's data records
+ * @param rcount The number of data records
+ */
+static void mxit_parse_cmd_media( struct MXitSession* session, struct record** records, int rcount )
+{
+ char type;
+ int size;
+
+ type = records[0]->fields[0]->data[0];
+ size = get_chunk_len( records[0]->fields[0]->data );
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_media (%i records) (%i bytes)\n", rcount, size );
+
+ /* supported chunked data types */
+ switch ( type ) {
+ case CP_CHUNK_CUSTOM : /* custom resource */
+ {
+ struct cr_chunk chunk;
+
+ /* decode the chunked data */
+ memset( &chunk, 0, sizeof( struct cr_chunk ) );
+ mxit_chunk_parse_cr( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+ purple_debug_info( MXIT_PLUGIN_ID, "chunk info id=%s handle=%s op=%i\n", chunk.id, chunk.handle, chunk.operation );
+
+ /* this is a splash-screen operation */
+ if ( strcmp( chunk.handle, HANDLE_SPLASH2 ) == 0 ) {
+ if ( chunk.operation == CR_OP_UPDATE ) { /* update the splash-screen */
+ struct splash_chunk *splash = chunk.resources->data; // TODO: Fix - assuming 1st resource is splash
+ gboolean clickable = ( g_list_length( chunk.resources ) > 1 ); // TODO: Fix - if 2 resources, then is clickable
+
+ if ( splash != NULL )
+ splash_update( session, chunk.id, splash->data, splash->datalen, clickable );
+ }
+ else if ( chunk.operation == CR_OP_REMOVE ) /* remove the splash-screen */
+ splash_remove( session );
+ }
+
+ /* cleanup custom resources */
+ g_list_foreach( chunk.resources, (GFunc)g_free, NULL );
+
+ }
+ break;
+
+ case CP_CHUNK_OFFER : /* file offer */
+ {
+ struct offerfile_chunk chunk;
+
+ /* decode the chunked data */
+ memset( &chunk, 0, sizeof( struct offerfile_chunk ) );
+ mxit_chunk_parse_offer( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+ /* process the offer */
+ mxit_xfer_rx_offer( session, chunk.username, chunk.filename, chunk.filesize, chunk.fileid );
+ }
+ break;
+
+ case CP_CHUNK_GET : /* get file response */
+ {
+ struct getfile_chunk chunk;
+
+ /* decode the chunked data */
+ memset( &chunk, 0, sizeof( struct getfile_chunk ) );
+ mxit_chunk_parse_get( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+ /* process the getfile */
+ mxit_xfer_rx_file( session, chunk.fileid, chunk.data, chunk.length );
+ }
+ break;
+
+ case CP_CHUNK_GET_AVATAR : /* get avatars */
+ {
+ struct getavatar_chunk chunk;
+
+ /* decode the chunked data */
+ memset( &chunk, 0, sizeof ( struct getavatar_chunk ) );
+ mxit_chunk_parse_get_avatar( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+ /* update avatar image */
+ if ( chunk.data ) {
+ purple_debug_info( MXIT_PLUGIN_ID, "updating avatar for contact '%s'\n", chunk.mxitid );
+ purple_buddy_icons_set_for_user( session->acc, chunk.mxitid, g_memdup( chunk.data, chunk.length), chunk.length, chunk.avatarid );
+ }
+
+ }
+ break;
+
+ case CP_CHUNK_SET_AVATAR :
+ /* this is a reply packet to a set avatar request. no action is required */
+ break;
+
+ case CP_CHUNK_DIRECT_SND :
+ /* this is a ack for a file send. no action is required */
+ break;
+
+ case CP_CHUNK_RECIEVED :
+ /* this is a ack for a file received. no action is required */
+ break;
+
+ default :
+ purple_debug_error( MXIT_PLUGIN_ID, "Unsupported chunked data packet type received (%i)\n", type );
+ break;
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Handle a redirect sent from the MXit server.
+ *
+ * @param session The MXit session object
+ * @param url The redirect information
+ */
+static void mxit_perform_redirect( struct MXitSession* session, const char* url )
+{
+ gchar** parts;
+ gchar** host;
+ int type;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s\n", url );
+
+ /* tokenize the URL string */
+ parts = g_strsplit( url, ";", 0 );
+
+ /* Part 1: protocol://host:port */
+ host = g_strsplit( parts[0], ":", 4 );
+ if ( strcmp( host[0], "socket" ) == 0 ) {
+ /* redirect to a MXit socket proxy */
+ g_strlcpy( session->server, &host[1][2], sizeof( session->server ) );
+ session->port = atoi( host[2] );
+ }
+ else {
+ purple_connection_error( session->con, _( "Cannot perform redirect using the specified protocol" ) );
+ goto redirect_fail;
+ }
+
+ /* Part 2: type of redirect */
+ type = atoi( parts[1] );
+ if ( type == CP_REDIRECT_PERMANENT ) {
+ /* permanent redirect, so save new MXit server and port */
+ purple_account_set_string( session->acc, MXIT_CONFIG_SERVER_ADDR, session->server );
+ purple_account_set_int( session->acc, MXIT_CONFIG_SERVER_PORT, session->port );
+ }
+
+ /* Part 3: message (optional) */
+ if ( parts[2] != NULL )
+ purple_connection_notice( session->con, parts[2] );
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s redirect to %s:%i\n",
+ ( type == CP_REDIRECT_PERMANENT ) ? "Permanent" : "Temporary", session->server, session->port );
+
+ /* perform the re-connect to the new MXit server */
+ mxit_reconnect( session );
+
+redirect_fail:
+ g_strfreev( parts );
+ g_strfreev( host );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a success response received from the MXit server.
+ *
+ * @param session The MXit session object
+ * @param packet The received packet
+ */
+static int process_success_response( struct MXitSession* session, struct rx_packet* packet )
+{
+ /* ignore ping/poll packets */
+ if ( ( packet->cmd != CP_CMD_PING ) && ( packet->cmd != CP_CMD_POLL ) )
+ session->last_rx = time( NULL );
+
+ /*
+ * when we pass the packet records to the next level for parsing
+ * we minus 3 records because 1) the first record is the packet
+ * type 2) packet reply status 3) the last record is bogus
+ */
+
+ /* packet command */
+ switch ( packet->cmd ) {
+
+ case CP_CMD_REGISTER :
+ /* fall through, when registeration successful, MXit will auto login */
+ case CP_CMD_LOGIN :
+ /* login response */
+ if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) {
+ mxit_parse_cmd_login( session, &packet->records[2], packet->rcount - 3 );
+ }
+ break;
+
+ case CP_CMD_LOGOUT :
+ /* logout response */
+ session->flags &= ~MXIT_FLAG_LOGGEDIN;
+ purple_account_disconnect( session->acc );
+
+ /* note:
+ * we do not prompt the user here for a reconnect, because this could be the user
+ * logging in with his phone. so we just disconnect the account otherwise
+ * mxit will start to bounce between the phone and pidgin. also could be a valid
+ * disconnect selected by the user.
+ */
+ return -1;
+
+ case CP_CMD_CONTACT :
+ /* contact update */
+ mxit_parse_cmd_contact( session, &packet->records[2], packet->rcount - 3 );
+ break;
+
+ case CP_CMD_PRESENCE :
+ /* presence update */
+ mxit_parse_cmd_presence(session, &packet->records[2], packet->rcount - 3 );
+ break;
+
+ case CP_CMD_RX_MSG :
+ /* incoming message (no bogus record) */
+ mxit_parse_cmd_message( session, &packet->records[2], packet->rcount - 2 );
+ break;
+
+ case CP_CMD_NEW_SUB :
+ /* new subscription request */
+ mxit_parse_cmd_new_sub( session, &packet->records[2], packet->rcount - 3 );
+ break;
+
+ case CP_CMD_MEDIA :
+ /* multi-media message */
+ mxit_parse_cmd_media( session, &packet->records[2], packet->rcount - 2 );
+ break;
+
+ case CP_CMD_EXTPROFILE_GET :
+ /* profile update */
+ mxit_parse_cmd_extprofile( session, &packet->records[2], packet->rcount - 2 );
+ break;
+
+ case CP_CMD_MOOD :
+ /* mood update */
+ case CP_CMD_UPDATE :
+ /* update contact information */
+ case CP_CMD_ALLOW :
+ /* allow subscription ack */
+ case CP_CMD_DENY :
+ /* deny subscription ack */
+ case CP_CMD_INVITE :
+ /* invite contact ack */
+ case CP_CMD_REMOVE :
+ /* remove contact ack */
+ case CP_CMD_TX_MSG :
+ /* outgoing message ack */
+ case CP_CMD_STATUS :
+ /* presence update ack */
+ case CP_CMD_GRPCHAT_CREATE :
+ /* create groupchat */
+ case CP_CMD_GRPCHAT_INVITE :
+ /* groupchat invite */
+ case CP_CMD_PING :
+ /* ping reply */
+ case CP_CMD_POLL :
+ /* HTTP poll reply */
+ case CP_CMD_EXTPROFILE_SET :
+ /* profile update */
+ case CP_CMD_SPLASHCLICK :
+ /* splash-screen clickthrough */
+ break;
+
+ default :
+ /* unknown packet */
+ purple_debug_error( MXIT_PLUGIN_ID, "Received unknown client packet (cmd = %i)\n", packet->cmd );
+ }
+
+ return 0;
+}
+
+
+/*------------------------------------------------------------------------
+ * Process an error response received from the MXit server.
+ *
+ * @param session The MXit session object
+ * @param packet The received packet
+ */
+static int process_error_response( struct MXitSession* session, struct rx_packet* packet )
+{
+ char errmsg[256];
+ const char* errdesc;
+
+ /* set the error description to be shown to the user */
+ if ( packet->errmsg )
+ errdesc = packet->errmsg;
+ else
+ errdesc = "An internal MXit server error occurred.";
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Error Reply %i:%s\n", packet->errcode, errdesc );
+
+ if ( packet->errcode == MXIT_ERRCODE_LOGGEDOUT ) {
+ /* we are not currently logged in, so we need to reconnect */
+ purple_connection_error( session->con, _( errmsg ) );
+ }
+
+ /* packet command */
+ switch ( packet->cmd ) {
+
+ case CP_CMD_REGISTER :
+ case CP_CMD_LOGIN :
+ if ( packet->errcode == MXIT_ERRCODE_REDIRECT ) {
+ mxit_perform_redirect( session, packet->errmsg );
+ return 0;
+ }
+ else {
+ sprintf( errmsg, "Login error: %s (%i)", errdesc, packet->errcode );
+ purple_connection_error( session->con, _( errmsg ) );
+ return -1;
+ }
+ case CP_CMD_LOGOUT :
+ sprintf( errmsg, "Logout error: %s (%i)", errdesc, packet->errcode );
+ purple_connection_error_reason( session->con, PURPLE_CONNECTION_ERROR_NAME_IN_USE, _( errmsg ) );
+ return -1;
+ case CP_CMD_CONTACT :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_RX_MSG :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_TX_MSG :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Sending Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_STATUS :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Status Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_MOOD :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Mood Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_KICK :
+ /*
+ * the MXit server sends this packet if we were idle for too long.
+ * to stop the server from closing this connection we need to resend
+ * the login packet.
+ */
+ mxit_send_login( session );
+ break;
+ case CP_CMD_INVITE :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Invitation Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_REMOVE :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Removal Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_ALLOW :
+ case CP_CMD_DENY :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Subscription Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_UPDATE :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Update Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_MEDIA :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "File Transfer Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_GRPCHAT_CREATE :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Cannot create MultiMx room" ), _( errdesc ) );
+ break;
+ case CP_CMD_GRPCHAT_INVITE :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "MultiMx Invitation Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_EXTPROFILE_GET :
+ case CP_CMD_EXTPROFILE_SET :
+ mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Profile Error" ), _( errdesc ) );
+ break;
+ case CP_CMD_SPLASHCLICK :
+ /* ignore error */
+ break;
+ case CP_CMD_PING :
+ case CP_CMD_POLL :
+ break;
+ default :
+ mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Error" ), _( errdesc ) );
+ break;
+ }
+
+ return 0;
+}
+
+
+/*========================================================================================================================
+ * Low-level Packet receive
+ */
+
+#ifdef DEBUG_PROTOCOL
+/*------------------------------------------------------------------------
+ * Dump a received packet structure.
+ *
+ * @param p The received packet
+ */
+static void dump_packet( struct rx_packet* p )
+{
+ struct record* r = NULL;
+ struct field* f = NULL;
+ int i;
+ int j;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "PACKET DUMP: (%i records)\n", p->rcount );
+
+ for ( i = 0; i < p->rcount; i++ ) {
+ r = p->records[i];
+ purple_debug_info( MXIT_PLUGIN_ID, "RECORD: (%i fields)\n", r->fcount );
+
+ for ( j = 0; j < r->fcount; j++ ) {
+ f = r->fields[j];
+ purple_debug_info( MXIT_PLUGIN_ID, "\tFIELD: (len=%i) '%s' \n", f->len, f->data );
+ }
+ }
+}
+#endif
+
+
+/*------------------------------------------------------------------------
+ * Free up memory used by a packet structure.
+ *
+ * @param p The received packet
+ */
+static void free_rx_packet( struct rx_packet* p )
+{
+ struct record* r = NULL;
+ struct field* f = NULL;
+ int i;
+ int j;
+
+ for ( i = 0; i < p->rcount; i++ ) {
+ r = p->records[i];
+
+ for ( j = 0; j < r->fcount; j++ ) {
+ g_free( f );
+ }
+ g_free( r->fields );
+ g_free( r );
+ }
+ g_free( p->records );
+}
+
+
+/*------------------------------------------------------------------------
+ * Add a new field to a record.
+ *
+ * @param r Parent record object
+ * @return The newly created field
+ */
+static struct field* add_field( struct record* r )
+{
+ struct field* field;
+
+ field = g_new0( struct field, 1 );
+
+ r->fields = realloc( r->fields, sizeof( struct field* ) * ( r->fcount + 1 ) );
+ r->fields[r->fcount] = field;
+ r->fcount++;
+
+ return field;
+}
+
+
+/*------------------------------------------------------------------------
+ * Add a new record to a packet.
+ *
+ * @param p The packet object
+ * @return The newly created record
+ */
+static struct record* add_record( struct rx_packet* p )
+{
+ struct record* rec;
+
+ rec = g_new0( struct record, 1 );
+
+ p->records = realloc( p->records, sizeof( struct record* ) * ( p->rcount + 1 ) );
+ p->records[p->rcount] = rec;
+ p->rcount++;
+
+ return rec;
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse the received byte stream into a proper client protocol packet.
+ *
+ * @param session The MXit session object
+ * @return Success (0) or Failure (!0)
+ */
+int mxit_parse_packet( struct MXitSession* session )
+{
+ struct rx_packet packet;
+ struct record* rec;
+ struct field* field;
+ gboolean pbreak;
+ unsigned int i;
+ int res = 0;
+
+#ifdef DEBUG_PROTOCOL
+ purple_debug_info( MXIT_PLUGIN_ID, "Received packet (%i bytes)\n", session->rx_i );
+ dump_bytes( session, session->rx_dbuf, session->rx_i );
+#endif
+
+ i = 0;
+ while ( i < session->rx_i ) {
+
+ /* create first record and field */
+ rec = NULL;
+ field = NULL;
+ memset( &packet, 0x00, sizeof( struct rx_packet ) );
+ rec = add_record( &packet );
+ pbreak = FALSE;
+
+ /* break up the received packet into fields and records for easy parsing */
+ while ( ( i < session->rx_i ) && ( !pbreak ) ) {
+
+ switch ( session->rx_dbuf[i] ) {
+ case CP_SOCK_REC_TERM :
+ /* new record */
+ if ( packet.rcount == 1 ) {
+ /* packet command */
+ packet.cmd = atoi( packet.records[0]->fields[0]->data );
+ }
+ else if ( packet.rcount == 2 ) {
+ /* special case: binary multimedia packets should not be parsed here */
+ if ( packet.cmd == CP_CMD_MEDIA ) {
+ /* add the chunked to new record */
+ rec = add_record( &packet );
+ field = add_field( rec );
+ field->data = &session->rx_dbuf[i + 1];
+ field->len = session->rx_i - i;
+ /* now skip the binary data */
+ res = get_chunk_len( field->data );
+ /* determine if we have more packets */
+ if ( res + 6 + i < session->rx_i ) {
+ /* we have more than one packet in this stream */
+ i += res + 6;
+ pbreak = TRUE;
+ }
+ else {
+ i = session->rx_i;
+ }
+ }
+ }
+ else if ( !field ) {
+ field = add_field( rec );
+ field->data = &session->rx_dbuf[i];
+ }
+ session->rx_dbuf[i] = '\0';
+ rec = add_record( &packet );
+ field = NULL;
+
+ break;
+ case CP_FLD_TERM :
+ /* new field */
+ session->rx_dbuf[i] = '\0';
+ if ( !field ) {
+ field = add_field( rec );
+ field->data = &session->rx_dbuf[i];
+ }
+ field = NULL;
+ break;
+ case CP_PKT_TERM :
+ /* packet is done! */
+ session->rx_dbuf[i] = '\0';
+ pbreak = TRUE;
+ break;
+ default :
+ /* skip non special characters */
+ if ( !field ) {
+ field = add_field( rec );
+ field->data = &session->rx_dbuf[i];
+ }
+ field->len++;
+ break;
+ }
+
+ i++;
+ }
+
+ if ( packet.rcount < 2 ) {
+ /* bad packet */
+ purple_connection_error( session->con, _( "Invalid packet received from MXit." ) );
+ free_rx_packet( &packet );
+ continue;
+ }
+
+ session->rx_dbuf[session->rx_i] = '\0';
+ packet.errcode = atoi( packet.records[1]->fields[0]->data );
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Packet received CMD:%i (%i)\n", packet.cmd, packet.errcode );
+#ifdef DEBUG_PROTOCOL
+ /* debug */
+ dump_packet( &packet );
+#endif
+
+ /* reset the out ack */
+ if ( session->outack == packet.cmd ) {
+ /* outstanding ack received from mxit server */
+ session->outack = 0;
+ }
+
+ /* check packet status */
+ if ( packet.errcode != MXIT_ERRCODE_SUCCESS ) {
+ /* error reply! */
+ if ( ( packet.records[1]->fcount > 1 ) && ( packet.records[1]->fields[1]->data ) )
+ packet.errmsg = packet.records[1]->fields[1]->data;
+ else
+ packet.errmsg = NULL;
+
+ res = process_error_response( session, &packet );
+ }
+ else {
+ /* success reply! */
+ res = process_success_response( session, &packet );
+ }
+
+ /* free up the packet resources */
+ free_rx_packet( &packet );
+ }
+
+ if ( session->outack == 0 )
+ mxit_manage_queue( session );
+
+ return res;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback when data is received from the MXit server.
+ *
+ * @param user_data The MXit session object
+ * @param source The file-descriptor on which data was received
+ * @param cond Condition which caused the callback (PURPLE_INPUT_READ)
+ */
+void mxit_cb_rx( gpointer user_data, gint source, PurpleInputCondition cond )
+{
+ struct MXitSession* session = (struct MXitSession*) user_data;
+ char ch;
+ int res;
+ int len;
+
+ if ( session->rx_state == RX_STATE_RLEN ) {
+ /* we are reading in the packet length */
+ len = read( session->fd, &ch, 1 );
+ if ( len < 0 ) {
+ /* connection error */
+ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x01)" ) );
+ return;
+ }
+ else if ( len == 0 ) {
+ /* connection closed */
+ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x02)" ) );
+ return;
+ }
+ else {
+ /* byte read */
+ if ( ch == CP_REC_TERM ) {
+ /* the end of the length record found */
+ session->rx_lbuf[session->rx_i] = '\0';
+ session->rx_res = atoi( &session->rx_lbuf[3] );
+ if ( session->rx_res > CP_MAX_PACKET ) {
+ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x03)" ) );
+ }
+ session->rx_state = RX_STATE_DATA;
+ session->rx_i = 0;
+ }
+ else {
+ /* still part of the packet length record */
+ session->rx_lbuf[session->rx_i] = ch;
+ session->rx_i++;
+ if ( session->rx_i >= sizeof( session->rx_lbuf ) ) {
+ /* malformed packet length record (too long) */
+ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x04)" ) );
+ return;
+ }
+ }
+ }
+ }
+ else if ( session->rx_state == RX_STATE_DATA ) {
+ /* we are reading in the packet data */
+ len = read( session->fd, &session->rx_dbuf[session->rx_i], session->rx_res );
+ if ( len < 0 ) {
+ /* connection error */
+ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x05)" ) );
+ return;
+ }
+ else if ( len == 0 ) {
+ /* connection closed */
+ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x06)" ) );
+ return;
+ }
+ else {
+ /* data read */
+ session->rx_i += len;
+ session->rx_res -= len;
+
+ if ( session->rx_res == 0 ) {
+ /* ok, so now we have read in the whole packet */
+ session->rx_state = RX_STATE_PROC;
+ }
+ }
+ }
+
+ if ( session->rx_state == RX_STATE_PROC ) {
+ /* we have a full packet, which we now need to process */
+ res = mxit_parse_packet( session );
+
+ if ( res == 0 ) {
+ /* we are still logged in */
+ session->rx_state = RX_STATE_RLEN;
+ session->rx_res = 0;
+ session->rx_i = 0;
+ }
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Log the user off MXit and close the connection
+ *
+ * @param session The MXit session object
+ */
+void mxit_close_connection( struct MXitSession* session )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_close_connection\n" );
+
+ if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) {
+ /* we are already closed */
+ return;
+ }
+ else if ( session->flags & MXIT_FLAG_LOGGEDIN ) {
+ /* we are currently logged in so we need to send a logout packet */
+ if ( !session->http ) {
+ mxit_send_logout( session );
+ }
+ session->flags &= ~MXIT_FLAG_LOGGEDIN;
+ }
+ session->flags &= ~MXIT_FLAG_CONNECTED;
+
+ /* cancel outstanding HTTP request */
+ if ( ( session->http ) && ( session->http_out_req ) ) {
+ purple_util_fetch_url_cancel( (PurpleUtilFetchUrlData*) session->http_out_req );
+ session->http_out_req = NULL;
+ }
+
+ /* remove the input cb function */
+ if ( session->con->inpa ) {
+ purple_input_remove( session->con->inpa );
+ session->con->inpa = 0;
+ }
+
+ /* remove HTTP poll timer */
+ if ( session->http_timer_id > 0 )
+ purple_timeout_remove( session->http_timer_id );
+
+ /* remove queue manager timer */
+ if ( session->q_timer > 0 )
+ purple_timeout_remove( session->q_timer );
+
+ /* remove all groupchat rooms */
+ while ( session->rooms != NULL ) {
+ struct multimx* multimx = (struct multimx *) session->rooms->data;
+
+ session->rooms = g_list_remove( session->rooms, multimx );
+
+ free( multimx );
+ }
+ g_list_free( session->rooms );
+ session->rooms = NULL;
+
+ /* remove all rx chats names */
+ while ( session->active_chats != NULL ) {
+ char* chat = (char*) session->active_chats->data;
+
+ session->active_chats = g_list_remove( session->active_chats, chat );
+
+ g_free( chat );
+ }
+ g_list_free( session->active_chats );
+ session->active_chats = NULL;
+
+ /* free profile information */
+ if ( session->profile )
+ free( session->profile );
+
+ /* free custom emoticons */
+ mxit_free_emoticon_cache( session );
+
+ /* free allocated memory */
+ g_free( session->encpwd );
+ session->encpwd = NULL;
+
+ /* flush all the commands still in the queue */
+ flush_queue( session );
+}
+
diff --git a/libpurple/protocols/mxit/protocol.h b/libpurple/protocols/mxit/protocol.h
new file mode 100644
index 0000000000..679bd4bc4c
--- /dev/null
+++ b/libpurple/protocols/mxit/protocol.h
@@ -0,0 +1,304 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- MXit client protocol implementation --
+ *
+ * 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
+ */
+
+#ifndef _MXIT_PROTO_H_
+#define _MXIT_PROTO_H_
+
+
+/* Client protocol constants */
+#define CP_SOCK_REC_TERM '\x00' /* socket record terminator */
+#define CP_HTTP_REC_TERM '\x26' /* http record terminator '&' */
+#define CP_FLD_TERM '\x01' /* field terminator */
+#define CP_PKT_TERM '\x02' /* packet terminator */
+
+
+#define CP_MAX_PACKET ( 1024 * 1024 ) /* maximum client protocol packet size (1 MiB) */
+#define CP_MAX_FILESIZE ( 150 * 1000 ) /* maximum client protocol file transfer size (150 KB) */
+#define MXIT_EMOTICON_SIZE 18 /* icon size for custom emoticons */
+#define CP_MAX_STATUS_MSG 250 /* maximum status message length (in characters) */
+
+/* Avatars */
+#define MXIT_AVATAR_SIZE 96 /* default avatar image size 96x96 */
+#define MXIT_AVATAR_TYPE "PNG" /* request avatars in this file type (only a suggestion) */
+#define MXIT_AVATAR_BITDEPT 24 /* request avatars with this bit depth (only a suggestion) */
+
+/* Protocol error codes */
+#define MXIT_ERRCODE_SUCCESS 0
+#define MXIT_ERRCODE_REDIRECT 16
+#define MXIT_ERRCODE_LOGGEDOUT 42
+
+/* MXit client features */
+#define MXIT_CF_NONE 0x000000
+#define MXIT_CF_FORMS 0x000001
+#define MXIT_CF_FILE_TRANSFER 0x000002
+#define MXIT_CF_CAMERA 0x000004
+#define MXIT_CF_COMMANDS 0x000008
+#define MXIT_CF_SMS 0x000010
+#define MXIT_CF_FILE_ACCESS 0x000020
+#define MXIT_CF_MIDP2 0x000040
+#define MXIT_CF_SKINS 0x000080
+#define MXIT_CF_AUDIO 0x000100
+#define MXIT_CF_ENCRYPTION 0x000200
+#define MXIT_CF_VOICE_REC 0x000400
+#define MXIT_CF_VECTOR_GFX 0x000800
+#define MXIT_CF_IMAGES 0x001000
+#define MXIT_CF_MARKUP 0x002000
+#define MXIT_CF_VIBES 0x004000
+#define MXIT_CF_SELECT_CONTACT 0x008000
+#define MXIT_CF_CUSTOM_EMO 0x010000
+#define MXIT_CF_ALERT_PROFILES 0x020000
+#define MXIT_CF_EXT_MARKUP 0x040000
+#define MXIT_CF_PLAIN_PWD 0x080000
+#define MXIT_CF_NO_GATEWAYS 0x100000
+
+/* Client features supported by this implementation */
+#define MXIT_CP_FEATURES ( MXIT_CF_FILE_TRANSFER | MXIT_CF_FILE_ACCESS | MXIT_CF_AUDIO | MXIT_CF_MARKUP | MXIT_CF_EXT_MARKUP | MXIT_CF_NO_GATEWAYS | MXIT_CF_IMAGES | MXIT_CF_COMMANDS | MXIT_CF_VIBES | MXIT_CF_MIDP2 )
+
+
+#define MXIT_PING_INTERVAL ( 5 * 60 ) /* ping the server after X seconds of being idle (5 minutes) */
+#define MXIT_ACK_TIMEOUT ( 30 ) /* timeout after waiting X seconds for an ack from the server (30 seconds) */
+
+/* MXit client version */
+#define MXIT_CP_DISTCODE "P" /* client distribution code (magic, do not touch!) */
+#define MXIT_CP_RELEASE "5.9.0" /* client protocol release version supported */
+#define MXIT_CP_ARCH "Y" /* client architecture series (Y not for Yoda but for PC-client) */
+#define MXIT_CLIENT_ID "LP" /* client ID as specified by MXit */
+#define MXIT_CP_PLATFORM "PURPLE" /* client platform */
+#define MXIT_CP_VERSION MXIT_CP_DISTCODE"-"MXIT_CP_RELEASE"-"MXIT_CP_ARCH"-"MXIT_CP_PLATFORM
+
+/* set operating system name */
+#if defined( __APPLE__ )
+#define MXIT_CP_OS "apple"
+#elif defined( _WIN32 )
+#define MXIT_CP_OS "windows"
+#elif defined( __linux__ )
+#define MXIT_CP_OS "linux"
+#else
+#define MXIT_CP_OS "unknown"
+#endif
+
+/* Client capabilities */
+#define MXIT_CP_CAP "utf8=true;cid="MXIT_CLIENT_ID
+
+/* Client settings */
+#define MAX_QUEUE_SIZE ( 1 << 4 ) /* tx queue size (16 packets) */
+#define MXIT_POPUP_WIN_NAME "MXit Notification" /* popup window name */
+#define MXIT_MAX_ATTRIBS 10 /* maximum profile attributes supported */
+#define MXIT_DEFAULT_LOCALE "en" /* default locale setting */
+#define MXIT_DEFAULT_LOC "planetpurple" /* the default location for registration */
+
+/* Client protocol commands */
+#define CP_CMD_LOGIN 0x0001 /* (1) login */
+#define CP_CMD_LOGOUT 0x0002 /* (2) logout */
+#define CP_CMD_CONTACT 0x0003 /* (3) get contacts */
+#define CP_CMD_UPDATE 0x0005 /* (5) update contact information */
+#define CP_CMD_INVITE 0x0006 /* (6) subscribe to new contact */
+#define CP_CMD_PRESENCE 0x0007 /* (7) get presence */
+#define CP_CMD_REMOVE 0x0008 /* (8) remove contact */
+#define CP_CMD_RX_MSG 0x0009 /* (9) get new messages */
+#define CP_CMD_TX_MSG 0x000A /* (10) send new message */
+#define CP_CMD_REGISTER 0x000B /* (11) register */
+//#define CP_CMD_PROFILE_SET 0x000C /* (12) set profile (DEPRECATED see CP_CMD_EXTPROFILE_SET) */
+#define CP_CMD_POLL 0x0011 /* (17) poll the HTTP server for an update */
+//#define CP_CMD_PROFILE_GET 0x001A /* (26) get profile (DEPRECATED see CP_CMD_EXTPROFILE_GET) */
+#define CP_CMD_MEDIA 0x001B /* (27) get multimedia message */
+#define CP_CMD_SPLASHCLICK 0x001F /* (31) splash-screen clickthrough */
+#define CP_CMD_STATUS 0x0020 /* (32) set shown presence & status */
+#define CP_CMD_MOOD 0x0029 /* (41) set mood */
+#define CP_CMD_KICK 0x002B /* (43) login kick */
+#define CP_CMD_GRPCHAT_CREATE 0x002C /* (44) create new groupchat */
+#define CP_CMD_GRPCHAT_INVITE 0x002D /* (45) add new groupchat member */
+#define CP_CMD_NEW_SUB 0x0033 /* (51) get new subscription */
+#define CP_CMD_ALLOW 0x0034 /* (52) allow subscription */
+#define CP_CMD_DENY 0x0037 /* (55) deny subscription */
+#define CP_CMD_EXTPROFILE_GET 0x0039 /* (57) get extended profile */
+#define CP_CMD_EXTPROFILE_SET 0x003A /* (58) set extended profile */
+#define CP_CMD_PING 0x03E8 /* (1000) ping (keepalive) */
+
+/* HTTP connection */
+#define MXIT_HTTP_POLL_MIN 7 /* minimum time between HTTP polls (seconds) */
+#define MXIT_HTTP_POLL_MAX ( 10 * 60 ) /* maximum time between HTTP polls (seconds) */
+
+/* receiver states */
+#define RX_STATE_RLEN 0x01 /* reading packet length section */
+#define RX_STATE_DATA 0x02 /* reading packet data section */
+#define RX_STATE_PROC 0x03 /* process read data */
+
+/* message flags */
+#define CP_MSG_ENCRYPTED 0x0010 /* message is encrypted */
+#define CP_MSG_MARKUP 0x0200 /* message may contain markup */
+#define CP_MSG_EMOTICON 0x0400 /* message may contain custom emoticons */
+
+/* redirect types */
+#define CP_REDIRECT_PERMANENT 1 /* permanent redirect */
+#define CP_REDIRECT_TEMPORARY 2 /* temporary redirect */
+
+/* message tx types */
+#define CP_MSGTYPE_NORMAL 0x01 /* normal message */
+#define CP_MSGTYPE_CHAT 0x02 /* chat message */
+#define CP_MSGTYPE_HEADLINE 0x03 /* headline message */
+#define CP_MSGTYPE_ERROR 0x04 /* error message */
+#define CP_MSGTYPE_GROUPCHAT 0x05 /* groupchat message */
+#define CP_MSGTYPE_FORM 0x06 /* mxit custom form */
+#define CP_MSGTYPE_COMMAND 0x07 /* mxit command */
+
+
+/* extended profile attribute fields */
+#define CP_PROFILE_BIRTHDATE "birthdate" /* Birthdate (String - ISO 8601 format) */
+#define CP_PROFILE_GENDER "gender" /* Gender (Boolean - 0=female, 1=male) */
+#define CP_PROFILE_HIDENUMBER "hidenumber" /* Hide Number (Boolean - 0=false, 1=true) */
+#define CP_PROFILE_FULLNAME "fullname" /* Fullname (UTF8 String) */
+#define CP_PROFILE_STATUS "statusmsg" /* Status Message (UTF8 String) */
+#define CP_PROFILE_PREVSTATUS "prevstatusmsgs" /* Previous Status Messages (UTF8 String) */
+#define CP_PROFILE_AVATAR "avatarid" /* Avatar ID (String) */
+#define CP_PROFILE_MODIFIED "lastmodified" /* Last-Modified timestamp */
+#define CP_PROFILE_TITLE "title" /* Title (UTF8 String) */
+#define CP_PROFILE_FIRSTNAME "firstname" /* First name (UTF8 String) */
+#define CP_PROFILE_LASTNAME "lastname" /* Last name (UTF8 String) */
+#define CP_PROFILE_EMAIL "email" /* Email address (UTF8 String) */
+#define CP_PROFILE_MOBILENR "mobilenumber" /* Mobile Number (UTF8 String) */
+
+/* extended profile field types */
+#define CP_PROF_TYPE_BOOL 0x02 /* boolean profile attribute type */
+#define CP_PROF_TYPE_INT 0x05 /* integer profile attribute type */
+#define CP_PROF_TYPE_UTF8 0x0A /* UTF8 string profile attribute type */
+#define CP_PROF_TYPE_DATE 0x0B /* date-time profile attribute type */
+
+
+/* define this to enable protocol debugging (very verbose logging) */
+#define DEBUG_PROTOCOL
+
+
+/* ======================================================================================= */
+
+struct MXitSession;
+
+/*------------------------------------------*/
+
+struct field {
+ char* data;
+ int len;
+};
+
+struct record {
+ struct field** fields;
+ int fcount;
+};
+
+struct rx_packet {
+ int cmd;
+ int errcode;
+ char* errmsg;
+ struct record** records;
+ int rcount;
+};
+
+struct tx_packet {
+ int cmd;
+ char header[256];
+ int headerlen;
+ char* data;
+ int datalen;
+};
+
+/*------------------------------------------*/
+
+
+/*
+ * A received message data object
+ */
+struct RXMsgData {
+ struct MXitSession* session; /* MXit session object */
+ char* from; /* the sender's name */
+ time_t timestamp; /* time at which the message was sent */
+ GString* msg; /* newly created message converted to libPurple formatting */
+ gboolean got_img; /* flag to say if this message got any images/emoticons embedded */
+ short img_count; /* the amount of images/emoticons still outstanding for the message */
+ int chatid; /* multimx chatroom id */
+ int flags; /* libPurple conversation flags */
+ gboolean converted; /* true if the message has been completely parsed and converted to libPurple markup */
+ gboolean processed; /* the message has been processed completely and should be freed up */
+};
+
+
+
+/*
+ * The packet transmission queue.
+ */
+struct tx_queue {
+ struct tx_packet* packets[MAX_QUEUE_SIZE]; /* array of packet pointers */
+ int count; /* number of packets queued */
+ int rd_i; /* queue current read index (queue offset for reading a packet) */
+ int wr_i; /* queue current write index (queue offset for adding new packet) */
+};
+
+
+/* ======================================================================================= */
+
+void mxit_popup( int type, const char* heading, const char* message );
+void mxit_strip_domain( char* username );
+gboolean find_active_chat( const GList* chats, const char* who );
+
+void mxit_cb_rx( gpointer data, gint source, PurpleInputCondition cond );
+gboolean mxit_manage_queue( gpointer user_data );
+gboolean mxit_manage_polling( gpointer user_data );
+
+void mxit_send_register( struct MXitSession* session );
+void mxit_send_login( struct MXitSession* session );
+void mxit_send_logout( struct MXitSession* session );
+void mxit_send_ping( struct MXitSession* session );
+void mxit_send_poll( struct MXitSession* session );
+
+void mxit_send_presence( struct MXitSession* session, int presence, const char* statusmsg );
+void mxit_send_mood( struct MXitSession* session, int mood );
+void mxit_send_message( struct MXitSession* session, const char* to, const char* msg, gboolean parse_markup );
+
+void mxit_send_extprofile_update( struct MXitSession* session, const char* password, unsigned int nr_attrib, const char* attributes );
+void mxit_send_extprofile_request( struct MXitSession* session, const char* username, unsigned int nr_attrib, const char* attribute[] );
+
+void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname );
+void mxit_send_remove( struct MXitSession* session, const char* username );
+void mxit_send_allow_sub( struct MXitSession* session, const char* username, const char* alias );
+void mxit_send_deny_sub( struct MXitSession* session, const char* username );
+void mxit_send_update_contact( struct MXitSession* session, const char* username, const char* alias, const char* groupname );
+void mxit_send_splashclick( struct MXitSession* session, const char* splashid );
+
+void mxit_send_file( struct MXitSession* session, const char* username, const char* filename, const unsigned char* buf, int buflen );
+void mxit_send_file_reject( struct MXitSession* session, const char* fileid );
+void mxit_send_file_accept( struct MXitSession* session, const char* fileid, int filesize, int offset );
+void mxit_send_file_received( struct MXitSession* session, const char* fileid, short status );
+void mxit_set_avatar( struct MXitSession* session, const unsigned char* avatar, int avatarlen );
+void mxit_get_avatar( struct MXitSession* session, const char* mxitId, const char* avatarId );
+
+void mxit_send_groupchat_create( struct MXitSession* session, const char* groupname, int nr_usernames, const char* usernames[] );
+void mxit_send_groupchat_invite( struct MXitSession* session, const char* roomid, int nr_usernames, const char* usernames[] );
+
+int mxit_parse_packet( struct MXitSession* session );
+void dump_bytes( struct MXitSession* session, const char* buf, int len );
+void mxit_close_connection( struct MXitSession* session );
+
+
+#endif /* _MXIT_PROTO_H_ */
+
diff --git a/libpurple/protocols/mxit/roster.c b/libpurple/protocols/mxit/roster.c
new file mode 100644
index 0000000000..26604802ee
--- /dev/null
+++ b/libpurple/protocols/mxit/roster.c
@@ -0,0 +1,722 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- user roster management (mxit contacts) --
+ *
+ * Pieter Loubser <libpurple@mxit.com>
+ *
+ * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
+ * <http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "purple.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "roster.h"
+
+
+struct contact_invite {
+ struct MXitSession* session; /* MXit session object */
+ struct contact* contact; /* The contact performing the invite */
+};
+
+
+/*========================================================================================================================
+ * Presence / Status
+ */
+
+/* statuses (reference: libpurple/status.h) */
+static struct status
+{
+ PurpleStatusPrimitive primative;
+ int mxit;
+ const char* id;
+ const char* name;
+} const mxit_statuses[] = {
+ /* primative, no, id, name */
+ { PURPLE_STATUS_OFFLINE, MXIT_PRESENCE_OFFLINE, "offline", "Offline" }, /* 0 */
+ { PURPLE_STATUS_AVAILABLE, MXIT_PRESENCE_ONLINE, "online", "Available" }, /* 1 */
+ { PURPLE_STATUS_AWAY, MXIT_PRESENCE_AWAY, "away", "Away" }, /* 2 */
+ { PURPLE_STATUS_AVAILABLE, MXIT_PRESENCE_AVAILABLE, "chat", "Chatty" }, /* 3 */
+ { PURPLE_STATUS_UNAVAILABLE, MXIT_PRESENCE_DND, "dnd", "Do Not Disturb" } /* 4 */
+};
+
+
+/*------------------------------------------------------------------------
+ * Return list of supported statuses. (see status.h)
+ *
+ * @param account The MXit account object
+ * @return List of PurpleStatusType
+ */
+GList* mxit_status_types( PurpleAccount* account )
+{
+ GList* statuslist = NULL;
+ PurpleStatusType* type;
+ unsigned int i;
+
+ for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) {
+ const struct status* status = &mxit_statuses[i];
+
+ /* add mxit status (reference: "libpurple/status.h") */
+ type = purple_status_type_new_with_attrs( status->primative, status->id, status->name, TRUE, TRUE, FALSE,
+ "message", _( "Message" ), purple_value_new( PURPLE_TYPE_STRING ),
+ NULL );
+
+ statuslist = g_list_append( statuslist, type );
+ }
+
+ return statuslist;
+}
+
+
+/*------------------------------------------------------------------------
+ * Returns the MXit presence code, given the unique status ID.
+ *
+ * @param id The status ID
+ * @return The MXit presence code
+ */
+int mxit_convert_presence( const char* id )
+{
+ unsigned int i;
+
+ for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) {
+ if ( strcmp( mxit_statuses[i].id, id ) == 0 ) /* status found! */
+ return mxit_statuses[i].mxit;
+ }
+
+ return -1;
+}
+
+
+/*------------------------------------------------------------------------
+ * Returns the MXit presence as a string, given the MXit presence ID.
+ *
+ * @param no The MXit presence I (see above)
+ * @return The presence as a text string
+ */
+const char* mxit_convert_presence_to_name( short no )
+{
+ unsigned int i;
+
+ for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) {
+ if ( mxit_statuses[i].mxit == no ) /* status found! */
+ return _( mxit_statuses[i].name );
+ }
+
+ return "";
+}
+
+
+/*========================================================================================================================
+ * Moods
+ */
+
+/*------------------------------------------------------------------------
+ * Returns the MXit mood as a string, given the MXit mood's ID.
+ *
+ * @param id The MXit mood ID (see roster.h)
+ * @return The mood as a text string
+ */
+const char* mxit_convert_mood_to_name( short id )
+{
+ switch ( id ) {
+ case MXIT_MOOD_ANGRY :
+ return _( "Angry" );
+ case MXIT_MOOD_EXCITED :
+ return _( "Excited" );
+ case MXIT_MOOD_GRUMPY :
+ return _( "Grumpy" );
+ case MXIT_MOOD_HAPPY :
+ return _( "Happy" );
+ case MXIT_MOOD_INLOVE :
+ return _( "In Love" );
+ case MXIT_MOOD_INVINCIBLE :
+ return _( "Invincible" );
+ case MXIT_MOOD_SAD :
+ return _( "Sad" );
+ case MXIT_MOOD_HOT :
+ return _( "Hot" );
+ case MXIT_MOOD_SICK :
+ return _( "Sick" );
+ case MXIT_MOOD_SLEEPY :
+ return _( "Sleepy" );
+ case MXIT_MOOD_NONE :
+ default :
+ return "";
+ }
+}
+
+
+/*========================================================================================================================
+ * Subscription Types
+ */
+
+/*------------------------------------------------------------------------
+ * Returns a Contact subscription type as a string.
+ *
+ * @param subtype The subscription type
+ * @return The subscription type as a text string
+ */
+const char* mxit_convert_subtype_to_name( short subtype )
+{
+ switch ( subtype ) {
+ case MXIT_SUBTYPE_BOTH :
+ return _( "Both" );
+ case MXIT_SUBTYPE_PENDING :
+ return _( "Pending" );
+ case MXIT_SUBTYPE_ASK :
+ return _( "Invited" );
+ case MXIT_SUBTYPE_REJECTED :
+ return _( "Rejected" );
+ case MXIT_SUBTYPE_DELETED :
+ return _( "Deleted" );
+ case MXIT_SUBTYPE_NONE :
+ return _( "None" );
+ default :
+ return "";
+ }
+}
+
+
+/*========================================================================================================================
+ * Calls from the MXit Protocol layer
+ */
+
+#if 0
+/*------------------------------------------------------------------------
+ * Dump a contact's info the the debug console.
+ *
+ * @param contact The contact
+ */
+static void dump_contact( struct contact* contact )
+{
+ purple_debug_info( MXIT_PLUGIN_ID, "CONTACT: name='%s', alias='%s', group='%s', type='%i', presence='%i', mood='%i'\n",
+ contact->username, contact->alias, contact->groupname, contact->type, contact->presence, contact->mood );
+}
+#endif
+
+
+#if 0
+/*------------------------------------------------------------------------
+ * Move a buddy from one group to another
+ *
+ * @param buddy the buddy to move between groups
+ * @param group the new group to move the buddy to
+ */
+static PurpleBuddy* mxit_update_buddy_group( struct MXitSession* session, PurpleBuddy* buddy, PurpleGroup* group )
+{
+ struct contact* contact = NULL;
+ PurpleGroup* current_group = purple_buddy_get_group( buddy );
+ PurpleBuddy* newbuddy = NULL;
+
+ /* make sure the groups actually differs */
+ if ( strcmp( current_group->name, group->name ) != 0 ) {
+ /* groupnames does not match, so we need to make the update */
+
+ purple_debug_info( MXIT_PLUGIN_ID, "Moving '%s' from group '%s' to '%s'\n", buddy->alias, current_group->name, group->name );
+
+ /*
+ * XXX: libPurple does not currently provide an API to change or rename the group name
+ * for a specific buddy. One option is to remove the buddy from the list and re-adding
+ * him in the new group, but by doing that makes the buddy go offline and then online
+ * again. This is really not ideal and very iretating, but how else then?
+ */
+
+ /* create new buddy */
+ newbuddy = purple_buddy_new( session->acc, buddy->name, buddy->alias );
+ newbuddy->proto_data = buddy->proto_data;
+ buddy->proto_data = NULL;
+
+ /* remove the buddy */
+ purple_blist_remove_buddy( buddy );
+
+ /* add buddy */
+ purple_blist_add_buddy( newbuddy, NULL, group, NULL );
+
+ /* now re-instate his presence again */
+ contact = newbuddy->proto_data;
+ if ( contact ) {
+
+ /* update the buddy's status (reference: "libpurple/prpl.h") */
+ if ( contact->statusMsg )
+ purple_prpl_got_user_status( session->acc, newbuddy->name, mxit_statuses[contact->presence].id, "message", contact->statusMsg, NULL );
+ else
+ purple_prpl_got_user_status( session->acc, newbuddy->name, mxit_statuses[contact->presence].id, NULL );
+
+ /* update avatar */
+ if ( contact->avatarId ) {
+ mxit_get_avatar( session, newbuddy->name, contact->avatarId );
+ g_free( contact->avatarId );
+ contact->avatarId = NULL;
+ }
+ }
+
+ return newbuddy;
+ }
+ else
+ return buddy;
+}
+#endif
+
+
+/*------------------------------------------------------------------------
+ * A contact update packet was received from the MXit server, so update the buddy's
+ * information.
+ *
+ * @param session The MXit session object
+ * @param contact The contact
+ */
+void mxit_update_contact( struct MXitSession* session, struct contact* contact )
+{
+ PurpleBuddy* buddy = NULL;
+ PurpleGroup* group = NULL;
+ const char* id = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_contact: user='%s' alias='%s' group='%s'\n", contact->username, contact->alias, contact->groupname );
+
+ /*
+ * libPurple requires all contacts to be in a group.
+ * So if this MXit contact isn't in a group, pretend it is.
+ */
+ if ( *contact->groupname == '\0' ) {
+ strcpy( contact->groupname, MXIT_DEFAULT_GROUP );
+ }
+
+ /* find or create a group for this contact */
+ group = purple_find_group( contact->groupname );
+ if ( !group )
+ group = purple_group_new( contact->groupname );
+
+ /* see if the buddy is not in the group already */
+ buddy = purple_find_buddy_in_group( session->acc, contact->username, group );
+ if ( !buddy ) {
+ /* buddy not found in the group */
+
+ /* lets try finding him in all groups */
+ buddy = purple_find_buddy( session->acc, contact->username );
+ if ( buddy ) {
+ /* ok, so we found him in another group. to switch him between groups we must delete him and add him again. */
+ purple_blist_remove_buddy( buddy );
+ buddy = NULL;
+ }
+
+ /* create new buddy */
+ buddy = purple_buddy_new( session->acc, contact->username, contact->alias );
+ buddy->proto_data = contact;
+
+ /* add new buddy to list */
+ purple_blist_add_buddy( buddy, NULL, group, NULL );
+ }
+ else {
+ /* buddy was found in the group */
+
+ /* now update the buddy's alias */
+ purple_blist_alias_buddy( buddy, contact->alias );
+
+ /* replace the buddy's contact struct */
+ if ( buddy->proto_data )
+ free( buddy->proto_data );
+ buddy->proto_data = contact;
+ }
+
+ /* load buddy's avatar id */
+ id = purple_buddy_icons_get_checksum_for_user( buddy );
+ if ( id )
+ contact->avatarId = g_strdup( id );
+ else
+ contact->avatarId = NULL;
+
+ /* update the buddy's status (reference: "libpurple/prpl.h") */
+ purple_prpl_got_user_status( session->acc, contact->username, mxit_statuses[contact->presence].id, NULL );
+}
+
+
+/*------------------------------------------------------------------------
+ * A presence update packet was received from the MXit server, so update the buddy's
+ * information.
+ *
+ * @param session The MXit session object
+ * @param username The contact which presence to update
+ * @param presence The new presence state for the contact
+ * @param mood The new mood for the contact
+ * @param customMood The custom mood identifier
+ * @param statusMsg This is the contact's status message
+ * @param avatarId This is the contact's avatar id
+ */
+void mxit_update_buddy_presence( struct MXitSession* session, const char* username, short presence, short mood, const char* customMood, const char* statusMsg, const char* avatarId )
+{
+ PurpleBuddy* buddy = NULL;
+ struct contact* contact = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: user='%s' presence=%i mood=%i customMood='%s' statusMsg='%s' avatar='%s'\n",
+ username, presence, mood, customMood, statusMsg, avatarId );
+
+ if ( ( presence < MXIT_PRESENCE_OFFLINE ) || ( presence > MXIT_PRESENCE_DND ) ) {
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: invalid presence state %i\n", presence );
+ return; /* ignore packet */
+ }
+
+ /* find the buddy information for this contact (reference: "libpurple/blist.h") */
+ buddy = purple_find_buddy( session->acc, username );
+ if ( !buddy ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: unable to find the buddy '%s'\n", username );
+ return;
+ }
+
+ contact = buddy->proto_data;
+ if ( !contact )
+ return;
+
+ contact->presence = presence;
+ contact->mood = mood;
+
+ g_strlcpy( contact->customMood, customMood, sizeof( contact->customMood ) );
+ // TODO: Download custom mood frame.
+
+ /* update status message */
+ if ( contact->statusMsg ) {
+ g_free( contact->statusMsg );
+ contact->statusMsg = NULL;
+ }
+ if ( statusMsg[0] != '\0' )
+ contact->statusMsg = g_strdup( statusMsg );
+
+ /* update avatarId */
+ if ( ( contact->avatarId ) && ( g_ascii_strcasecmp( contact->avatarId, avatarId ) == 0 ) ) {
+ /* avatar has not changed - do nothing */
+ }
+ else if ( avatarId[0] != '\0' ) { /* avatar has changed */
+ if ( contact->avatarId )
+ g_free( contact->avatarId );
+ contact->avatarId = g_strdup( avatarId );
+
+ /* Send request to download new avatar image */
+ mxit_get_avatar( session, username, avatarId );
+ }
+ else /* clear current avatar */
+ purple_buddy_icons_set_for_user( session->acc, username, NULL, 0, NULL );
+
+ /* update the buddy's status (reference: "libpurple/prpl.h") */
+ if ( contact->statusMsg )
+ purple_prpl_got_user_status( session->acc, username, mxit_statuses[contact->presence].id, "message", contact->statusMsg, NULL );
+ else
+ purple_prpl_got_user_status( session->acc, username, mxit_statuses[contact->presence].id, NULL );
+}
+
+
+/*------------------------------------------------------------------------
+ * update the blist cached by libPurple. We need to do this to keep
+ * libPurple and MXit's rosters in sync with each other.
+ *
+ * @param session The MXit session object
+ */
+void mxit_update_blist( struct MXitSession* session )
+{
+ PurpleBuddy* buddy = NULL;
+ GSList* list = NULL;
+ unsigned int i;
+
+ /* remove all buddies we did not receive a roster update for.
+ * these contacts must have been removed from another client */
+ list = purple_find_buddies( session->acc, NULL );
+
+ for ( i = 0; i < g_slist_length( list ); i++ ) {
+ buddy = g_slist_nth_data( list, i );
+
+ if ( !buddy->proto_data ) {
+ /* this buddy should be removed, because we did not receive him in our roster update from MXit */
+ purple_debug_info( MXIT_PLUGIN_ID, "Removed 'old' buddy from the blist '%s' (%s)\n", buddy->alias, buddy->name );
+ purple_blist_remove_buddy( buddy );
+ }
+ }
+
+ /* tell the UI to update the blist */
+ purple_blist_add_account( session->acc );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user authorized an invite (subscription request).
+ *
+ * @param user_data Object associated with the invite
+ */
+static void mxit_cb_buddy_auth( gpointer user_data )
+{
+ struct contact_invite* invite = (struct contact_invite*) user_data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_buddy_auth '%s'\n", invite->contact->username );
+
+ /* send a allow subscription packet to MXit */
+ mxit_send_allow_sub( invite->session, invite->contact->username, invite->contact->alias );
+
+ /* freeup invite object */
+ if ( invite->contact->msg )
+ g_free( invite->contact->msg );
+ g_free( invite->contact );
+ g_free( invite );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user rejected an invite (subscription request).
+ *
+ * @param user_data Object associated with the invite
+ */
+static void mxit_cb_buddy_deny( gpointer user_data )
+{
+ struct contact_invite* invite = (struct contact_invite*) user_data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_buddy_deny '%s'\n", invite->contact->username );
+
+ /* send a deny subscription packet to MXit */
+ mxit_send_deny_sub( invite->session, invite->contact->username );
+
+ /* freeup invite object */
+ if ( invite->contact->msg )
+ g_free( invite->contact->msg );
+ g_free( invite->contact );
+ g_free( invite );
+}
+
+
+/*------------------------------------------------------------------------
+ * A new subscription request packet was received from the MXit server.
+ * Prompt user to accept or reject it.
+ *
+ * @param session The MXit session object
+ * @param contact The contact performing the invite
+ */
+void mxit_new_subscription( struct MXitSession* session, struct contact* contact )
+{
+ struct contact_invite* invite;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_new_subscription from '%s' (%s)\n", contact->username, contact->alias );
+
+ invite = g_new0( struct contact_invite, 1 );
+ invite->session = session;
+ invite->contact = contact;
+
+ /* (reference: "libpurple/account.h") */
+ purple_account_request_authorization( session->acc, contact->username, NULL, contact->alias, contact->msg, FALSE, mxit_cb_buddy_auth, mxit_cb_buddy_deny, invite );
+}
+
+
+/*------------------------------------------------------------------------
+ * Return TRUE if this is a MXit Chatroom contact.
+ *
+ * @param session The MXit session object
+ * @param username The username of the contact
+ */
+gboolean is_mxit_chatroom_contact( struct MXitSession* session, const char* username )
+{
+ PurpleBuddy* buddy;
+ struct contact* contact = NULL;
+
+ /* find the buddy */
+ buddy = purple_find_buddy( session->acc, username );
+ if ( !buddy ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "is_mxit_chatroom_contact: unable to find the buddy '%s'\n", username );
+ return FALSE;
+ }
+
+ contact = buddy->proto_data;
+ if ( !contact )
+ return FALSE;
+
+ return ( contact->type == MXIT_TYPE_CHATROOM );
+}
+
+
+/*========================================================================================================================
+ * Callbacks from libpurple
+ */
+
+/*------------------------------------------------------------------------
+ * The user has added a buddy to the list, so send an invite request.
+ *
+ * @param gc The connection object
+ * @param buddy The new buddy
+ * @param group The group of the new buddy
+ */
+void mxit_add_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ GSList* list = NULL;
+ PurpleBuddy* mxbuddy = NULL;
+ unsigned int i;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy '%s' (group='%s')\n", buddy->name, group->name );
+
+ list = purple_find_buddies( session->acc, buddy->name );
+ if ( g_slist_length( list ) == 1 ) {
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy (scenario 1) (list:%i)\n", g_slist_length( list ) );
+ /*
+ * we only send an invite to MXit when the user is not already inside our
+ * blist. this is done because purple does an add_buddy() call when
+ * you accept an invite. so in that case the user is already
+ * in our blist and ready to be chatted to.
+ */
+ mxit_send_invite( session, buddy->name, buddy->alias, group->name );
+ }
+ else {
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy (scenario 2) (list:%i)\n", g_slist_length( list ) );
+ /*
+ * we already have the buddy in our list, so we will only update
+ * his information here and not send another invite message
+ */
+
+ /* find the correct buddy */
+ for ( i = 0; i < g_slist_length( list ); i++ ) {
+ mxbuddy = g_slist_nth_data( list, i );
+
+ if ( mxbuddy->proto_data != NULL ) {
+ /* this is our REAL MXit buddy! */
+
+ /* now update the buddy's alias */
+ purple_blist_alias_buddy( mxbuddy, buddy->alias );
+
+ /* now update the buddy's group */
+// mxbuddy = mxit_update_buddy_group( session, mxbuddy, group );
+
+ /* send the update to the MXit server */
+ mxit_send_update_contact( session, mxbuddy->name, mxbuddy->alias, group->name );
+ }
+ }
+ }
+
+ /*
+ * we remove the buddy here from the buddy list because the MXit server
+ * will send us a proper contact update packet if this succeeds. now
+ * we do not have to worry about error handling in case of adding an
+ * invalid contact. so the user will still see the contact as offline
+ * until he eventually accepts the invite.
+ */
+ purple_blist_remove_buddy( buddy );
+
+ g_slist_free( list );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has removed a buddy from the list.
+ *
+ * @param gc The connection object
+ * @param buddy The buddy being removed
+ * @param group The group the buddy was in
+ */
+void mxit_remove_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_remove_buddy '%s'\n", buddy->name );
+
+ mxit_send_remove( session, buddy->name );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user changed the buddy's alias.
+ *
+ * @param gc The connection object
+ * @param who The username of the buddy
+ * @param alias The new alias
+ */
+void mxit_buddy_alias( PurpleConnection* gc, const char* who, const char* alias )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ PurpleBuddy* buddy = NULL;
+ PurpleGroup* group = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_buddy_alias '%s' to '%s\n", who, alias );
+
+ /* find the buddy */
+ buddy = purple_find_buddy( session->acc, who );
+ if ( !buddy ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_alias: unable to find the buddy '%s'\n", who );
+ return;
+ }
+
+ /* find buddy group */
+ group = purple_buddy_get_group( buddy );
+ if ( !group ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_alias: unable to find the group for buddy '%s'\n", who );
+ return;
+ }
+
+ mxit_send_update_contact( session, who, alias, group->name );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user changed the group for a single buddy.
+ *
+ * @param gc The connection object
+ * @param who The username of the buddy
+ * @param old_group The old group's name
+ * @param new_group The new group's name
+ */
+void mxit_buddy_group( PurpleConnection* gc, const char* who, const char* old_group, const char* new_group )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ PurpleBuddy* buddy = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_buddy_group from '%s' to '%s'\n", old_group, new_group );
+
+ /* find the buddy */
+ buddy = purple_find_buddy( session->acc, who );
+ if ( !buddy ) {
+ purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_group: unable to find the buddy '%s'\n", who );
+ return;
+ }
+
+ mxit_send_update_contact( session, who, buddy->alias, new_group );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has selected to rename a group, so update all contacts in that
+ * group.
+ *
+ * @param gc The connection object
+ * @param old_name The old group name
+ * @param group The updated group object
+ * @param moved_buddies The buddies affected by the rename
+ */
+void mxit_rename_group( PurpleConnection* gc, const char* old_name, PurpleGroup* group, GList* moved_buddies )
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ PurpleBuddy* buddy = NULL;
+ GList* item = NULL;
+
+ purple_debug_info( MXIT_PLUGIN_ID, "mxit_rename_group from '%s' to '%s\n", old_name, group->name );
+
+ // TODO: Might be more efficient to use the "rename group" command (cmd=29).
+
+ /* loop through all the contacts in the group and send updates */
+ item = moved_buddies;
+ while ( item ) {
+ buddy = item->data;
+ mxit_send_update_contact( session, buddy->name, buddy->alias, group->name );
+ item = g_list_next( item );
+ }
+}
+
diff --git a/libpurple/protocols/mxit/roster.h b/libpurple/protocols/mxit/roster.h
new file mode 100644
index 0000000000..27c022a280
--- /dev/null
+++ b/libpurple/protocols/mxit/roster.h
@@ -0,0 +1,139 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- user roster management (mxit contacts) --
+ *
+ * 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
+ */
+
+#ifndef _MXIT_ROSTER_H_
+#define _MXIT_ROSTER_H_
+
+
+/* MXit contact presence states */
+#define MXIT_PRESENCE_OFFLINE 0x00
+#define MXIT_PRESENCE_ONLINE 0x01
+#define MXIT_PRESENCE_AWAY 0x02
+#define MXIT_PRESENCE_AVAILABLE 0x03
+#define MXIT_PRESENCE_DND 0x04
+
+
+/* MXit contact types */
+#define MXIT_TYPE_MXIT 0x00
+#define MXIT_TYPE_JABBER 0x01
+#define MXIT_TYPE_MSN 0x02
+#define MXIT_TYPE_YAHOO 0x03
+#define MXIT_TYPE_ICQ 0x04
+#define MXIT_TYPE_AIM 0x05
+#define MXIT_TYPE_QQ 0x06
+#define MXIT_TYPE_WV 0x07
+#define MXIT_TYPE_BOT 0x08
+#define MXIT_TYPE_CHATROOM 0x09
+#define MXIT_TYPE_SMS 0x0A
+#define MXIT_TYPE_GROUP 0x0B
+#define MXIT_TYPE_GALLERY 0x0C
+#define MXIT_TYPE_INFO 0x0D
+#define MXIT_TYPE_MULTIMX 0x0E
+#define MXIT_TYPE_HYBRID 0x0F
+
+
+/* MXit contact moods */
+#define MXIT_MOOD_NONE 0x00
+#define MXIT_MOOD_ANGRY 0x01
+#define MXIT_MOOD_EXCITED 0x02
+#define MXIT_MOOD_GRUMPY 0x03
+#define MXIT_MOOD_HAPPY 0x04
+#define MXIT_MOOD_INLOVE 0x05
+#define MXIT_MOOD_INVINCIBLE 0x06
+#define MXIT_MOOD_SAD 0x07
+#define MXIT_MOOD_HOT 0x08
+#define MXIT_MOOD_SICK 0x09
+#define MXIT_MOOD_SLEEPY 0x0A
+
+
+/* MXit contact flags */
+#define MXIT_CFLAG_HIDDEN 0x02
+#define MXIT_CFLAG_GATEWAY 0x04
+#define MXIT_CFLAG_FOCUS_SEND_BLANK 0x20000
+
+
+/* Subscription types */
+#define MXIT_SUBTYPE_BOTH 'B'
+#define MXIT_SUBTYPE_PENDING 'P'
+#define MXIT_SUBTYPE_ASK 'A'
+#define MXIT_SUBTYPE_REJECTED 'R'
+#define MXIT_SUBTYPE_DELETED 'D'
+#define MXIT_SUBTYPE_NONE 'N'
+
+
+/* client protocol constants */
+#define MXIT_CP_MAX_JID_LEN 64
+#define MXIT_CP_MAX_GROUP_LEN 32
+#define MXIT_CP_MAX_ALIAS_LEN 48
+
+#define MXIT_DEFAULT_GROUP "MXit"
+
+
+/*
+ * a MXit contact
+ */
+struct contact {
+ char username[MXIT_CP_MAX_JID_LEN+1]; /* unique contact name (with domain) */
+ char alias[MXIT_CP_MAX_GROUP_LEN+1]; /* contact alias (what will be seen) */
+ char groupname[MXIT_CP_MAX_ALIAS_LEN+1]; /* contact group name */
+
+ short type; /* contact type */
+ short mood; /* contact current mood */
+ int flags; /* contact flags */
+ short presence; /* presence state */
+ short subtype; /* subscription type */
+
+ char* msg; /* invite message */
+
+ char customMood[16]; /* custom mood */
+ char* statusMsg; /* status message */
+ char* avatarId; /* avatarId */
+};
+
+/* Presence / Status */
+GList* mxit_status_types( PurpleAccount* account );
+int mxit_convert_presence( const char* id );
+const char* mxit_convert_presence_to_name( short no );
+const char* mxit_convert_subtype_to_name( short subtype );
+
+/* Moods */
+const char* mxit_convert_mood_to_name( short id );
+
+/* MXit Protocol callbacks */
+void mxit_update_contact( struct MXitSession* session, struct contact* contact );
+void mxit_update_buddy_presence( struct MXitSession* session, const char* username, short presence, short mood, const char* customMood, const char* statusMsg, const char* avatarId );
+void mxit_new_subscription( struct MXitSession* session, struct contact* contact );
+void mxit_update_blist( struct MXitSession* session );
+gboolean is_mxit_chatroom_contact( struct MXitSession* session, const char* username );
+
+/* libPurple callbacks */
+void mxit_add_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group );
+void mxit_remove_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group );
+void mxit_buddy_alias( PurpleConnection* gc, const char* who, const char* alias );
+void mxit_buddy_group( PurpleConnection* gc, const char* who, const char* old_group, const char* new_group );
+void mxit_rename_group( PurpleConnection* gc, const char* old_name, PurpleGroup* group, GList* moved_buddies );
+
+
+#endif /* _MXIT_ROSTER_H_ */
diff --git a/libpurple/protocols/mxit/splashscreen.c b/libpurple/protocols/mxit/splashscreen.c
new file mode 100644
index 0000000000..d4a86edfac
--- /dev/null
+++ b/libpurple/protocols/mxit/splashscreen.c
@@ -0,0 +1,223 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- splash screens --
+ *
+ * Andrew Victor <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 <libgen.h>
+#include <glib/gstdio.h>
+
+#include "purple.h"
+#include "imgstore.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "splashscreen.h"
+
+
+/*------------------------------------------------------------------------
+ * Return the ID of the current splash-screen.
+ *
+ * @param session The MXit session object
+ * @return The ID of the splash-screen (or NULL if no splash-screen)
+ */
+const char* splash_current(struct MXitSession* session)
+{
+ const char* splashId = purple_account_get_string(session->acc, MXIT_CONFIG_SPLASHID, NULL);
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Current splashId: '%s'\n", splashId);
+
+ if ((splashId != NULL) && (*splashId != '\0'))
+ return splashId;
+ else
+ return NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Indicate if splash-screen popups are enabled.
+ *
+ * @param session The MXit session object
+ * @return TRUE if the popup is enabled.
+ */
+gboolean splash_popup_enabled(struct MXitSession* session)
+{
+ return purple_account_get_bool(session->acc, MXIT_CONFIG_SPLASHPOPUP, DEFAULT_SPLASH_POPUP);
+}
+
+
+/*------------------------------------------------------------------------
+ * Return if the current splash-screen is clickable.
+ *
+ * @param session The MXit session object
+ * @return TRUE or FALSE
+ */
+static gboolean splash_clickable(struct MXitSession* session)
+{
+ return purple_account_get_bool(session->acc, MXIT_CONFIG_SPLASHCLICK, FALSE);
+}
+
+
+/*------------------------------------------------------------------------
+ * Remove the stored splash-screen (if it exists).
+ *
+ * @param session The MXit session object
+ */
+void splash_remove(struct MXitSession* session)
+{
+ const char* splashId = NULL;
+ char* filename;
+
+ /* Get current splash ID */
+ splashId = splash_current(session);
+
+ if (splashId != NULL) {
+ purple_debug_info(MXIT_PLUGIN_ID, "Removing splashId: '%s'\n", splashId);
+
+ /* Delete stored splash image */
+ filename = g_strdup_printf("%s/mxit/%s.png", purple_user_dir(), splashId);
+ g_unlink(filename);
+ g_free(filename);
+
+ /* Clear current splash ID from settings */
+ purple_account_set_string(session->acc, MXIT_CONFIG_SPLASHID, "");
+ purple_account_set_bool(session->acc, MXIT_CONFIG_SPLASHCLICK, FALSE);
+ }
+}
+
+
+/*------------------------------------------------------------------------
+ * Save a new splash-screen for later display.
+ *
+ * @param session The MXit session object
+ * @param splashID The ID of the splash-screen
+ * @param data Splash-screen image data (PNG format)
+ * @param datalen Splash-screen image data size
+ */
+void splash_update(struct MXitSession* session, const char* splashId, const char* data, int datalen, gboolean clickable)
+{
+ char* dir;
+ char* filename;
+
+ /* Remove the current splash-screen */
+ splash_remove(session);
+
+ /* Save the new splash image */
+ dir = g_strdup_printf("%s/mxit", purple_user_dir());
+ purple_build_dir(dir, S_IRUSR | S_IWUSR | S_IXUSR); /* ensure directory exists */
+
+ filename = g_strdup_printf("%s/%s.png", dir, splashId);
+ if (purple_util_write_data_to_file_absolute(filename, data, datalen)) {
+ /* Store new splash-screen ID to settings */
+ purple_account_set_string(session->acc, MXIT_CONFIG_SPLASHID, splashId);
+ purple_account_set_bool(session->acc, MXIT_CONFIG_SPLASHCLICK, clickable );
+ }
+
+ g_free(dir);
+ g_free(filename);
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has clicked OK on the Splash request form.
+ *
+ * @param gc The connection object
+ * @param fields The list of fields in the accepted form
+ */
+static void splash_click_ok(PurpleConnection* gc, PurpleRequestFields* fields)
+{
+ struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+ const char* splashId;
+
+ /* Get current splash ID */
+ splashId = splash_current(session);
+ if (!splashId)
+ return;
+
+ /* if is clickable, then send click event */
+ if (splash_clickable(session))
+ mxit_send_splashclick(session, splashId);
+}
+
+
+/*------------------------------------------------------------------------
+ * Display the current splash-screen.
+ *
+ * @param session The MXit session object
+ */
+void splash_display(struct MXitSession* session)
+{
+ const char* splashId = NULL;
+ char* filename;
+ gchar* imgdata;
+ gsize imglen;
+ int imgid = -1;
+
+ /* Get current splash ID */
+ splashId = splash_current(session);
+ if (splashId == NULL) /* no splash-screen */
+ return;
+
+ purple_debug_info(MXIT_PLUGIN_ID, "Display Splash: '%s'\n", splashId);
+
+ /* Load splash-screen image from file */
+ filename = g_strdup_printf("%s/mxit/%s.png", purple_user_dir(), splashId);
+ if (g_file_get_contents(filename, &imgdata, &imglen, NULL)) {
+ char buf[128];
+
+ /* Add splash-image to imagestore */
+ imgid = purple_imgstore_add_with_id(g_memdup(imgdata, imglen), imglen, NULL);
+
+ /* Generate and display message */
+ g_snprintf(buf, sizeof(buf), "<img id=\"%d\">", imgid);
+
+ /* Open a request-type popup to display the image */
+ {
+ PurpleRequestFields* fields;
+ PurpleRequestFieldGroup* group;
+ PurpleRequestField* field;
+
+ fields = purple_request_fields_new();
+ group = purple_request_field_group_new(NULL);
+ purple_request_fields_add_group(fields, group);
+
+ field = purple_request_field_image_new("splash", "", imgdata, imglen); /* add splash image */
+ purple_request_field_group_add_field(group, field);
+
+ if (splash_clickable(session)) {
+ purple_request_fields(session->con, _("MXit Advertising"), NULL, NULL, fields,
+ _("More Information"), G_CALLBACK(splash_click_ok), _("Close"), NULL, session->acc, NULL, NULL, session->con);
+ }
+ else {
+ purple_request_fields(session->con, _("MXit Advertising"), NULL, NULL, fields,
+ _("Continue"), G_CALLBACK(splash_click_ok), _("Close"), NULL, session->acc, NULL, NULL, session->con);
+ }
+ }
+
+ /* Release reference to image */
+ purple_imgstore_unref_by_id(imgid);
+
+ g_free(imgdata);
+ }
+
+ g_free(filename);
+}
diff --git a/libpurple/protocols/mxit/splashscreen.h b/libpurple/protocols/mxit/splashscreen.h
new file mode 100644
index 0000000000..55b7fc3116
--- /dev/null
+++ b/libpurple/protocols/mxit/splashscreen.h
@@ -0,0 +1,59 @@
+/*
+ * MXit Protocol libPurple Plugin
+ *
+ * -- splash screens --
+ *
+ * Andrew Victor <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
+ */
+
+#ifndef _MXIT_SPLASHSCREEN_H_
+#define _MXIT_SPLASHSCREEN_H_
+
+#define HANDLE_SPLASH1 "plas1.png"
+#define HANDLE_SPLASH2 "plas2.png"
+
+#define DEFAULT_SPLASH_POPUP FALSE /* disabled by default */
+
+/*
+ * Return the ID of the current splash-screen.
+ */
+const char* splash_current(struct MXitSession* session);
+
+/*
+ * Indicate if splash-screen popups are enabled.
+ */
+gboolean splash_popup_enabled();
+
+/*
+ * Save a new splash-screen.
+ */
+void splash_update(struct MXitSession* session, const char* splashId, const char* data, int datalen, gboolean clickable);
+
+/*
+ * Remove the stored splash-screen (if it exists).
+ */
+void splash_remove(struct MXitSession* session);
+
+/*
+ * Display the current splash-screen.
+ */
+void splash_display(struct MXitSession* session);
+
+#endif /* _MXIT_SPLASHSCREEN_H_ */
diff --git a/pidgin/gtkmain.c b/pidgin/gtkmain.c
index bbbbbcbf5c..4d843de7c0 100644
--- a/pidgin/gtkmain.c
+++ b/pidgin/gtkmain.c
@@ -541,8 +541,8 @@ int main(int argc, char *argv[])
GIOStatus signal_status;
#ifndef DEBUG
char *segfault_message_tmp;
- GError *error = NULL;
#endif
+ GError *error = NULL;
#endif
int opt;
gboolean gui_check;
diff --git a/pidgin/gtkprefs.c b/pidgin/gtkprefs.c
index b6ff7a7311..fb16521841 100644
--- a/pidgin/gtkprefs.c
+++ b/pidgin/gtkprefs.c
@@ -1134,7 +1134,7 @@ theme_page(void)
gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
- gtk_box_pack_start(GTK_BOX(ret), label, FALSE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0);
gtk_widget_show(label);
sw = gtk_scrolled_window_new(NULL,NULL);
diff --git a/pidgin/pixmaps/Makefile.am b/pidgin/pixmaps/Makefile.am
index 4e84aaafff..5583bfc05c 100644
--- a/pidgin/pixmaps/Makefile.am
+++ b/pidgin/pixmaps/Makefile.am
@@ -236,6 +236,7 @@ PROTOCOLS_16 = \
protocols/16/jabber.png \
protocols/16/meanwhile.png \
protocols/16/msn.png \
+ protocols/16/mxit.png \
protocols/16/myspace.png \
protocols/16/qq.png \
protocols/16/silc.png \
@@ -308,6 +309,7 @@ PROTOCOLS_48 = \
protocols/48/jabber.png \
protocols/48/meanwhile.png \
protocols/48/msn.png \
+ protocols/48/mxit.png \
protocols/48/myspace.png \
protocols/48/qq.png \
protocols/48/silc.png \
@@ -326,6 +328,7 @@ PROTOCOLS_SCALABLE = \
protocols/scalable/jabber.svg \
protocols/scalable/meanwhile.svg \
protocols/scalable/msn.svg \
+ protocols/scalable/mxit.svg \
protocols/scalable/qq.svg \
protocols/scalable/silc.svg \
protocols/scalable/simple.svg \
diff --git a/pidgin/pixmaps/protocols/16/mxit.png b/pidgin/pixmaps/protocols/16/mxit.png
new file mode 100644
index 0000000000..7ae49a17f6
--- /dev/null
+++ b/pidgin/pixmaps/protocols/16/mxit.png
Binary files differ
diff --git a/pidgin/pixmaps/protocols/22/mxit.png b/pidgin/pixmaps/protocols/22/mxit.png
new file mode 100644
index 0000000000..0e2bf38b05
--- /dev/null
+++ b/pidgin/pixmaps/protocols/22/mxit.png
Binary files differ
diff --git a/pidgin/pixmaps/protocols/48/mxit.png b/pidgin/pixmaps/protocols/48/mxit.png
new file mode 100644
index 0000000000..2b05a435a4
--- /dev/null
+++ b/pidgin/pixmaps/protocols/48/mxit.png
Binary files differ
diff --git a/pidgin/pixmaps/protocols/scalable/mxit.svg b/pidgin/pixmaps/protocols/scalable/mxit.svg
new file mode 100644
index 0000000000..3d9c0cc5ef
--- /dev/null
+++ b/pidgin/pixmaps/protocols/scalable/mxit.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
+<image overflow="visible" width="34" height="39" xlink:href="data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/7AARRHVja3kAAQAEAAAAHgAA/+4AIUFkb2JlAGTAAAAAAQMA
+EAMCAwYAAAH0AAACSgAAA6n/2wCEABALCwsMCxAMDBAXDw0PFxsUEBAUGx8XFxcXFx8eFxoaGhoX
+Hh4jJSclIx4vLzMzLy9AQEBAQEBAQEBAQEBAQEABEQ8PERMRFRISFRQRFBEUGhQWFhQaJhoaHBoa
+JjAjHh4eHiMwKy4nJycuKzU1MDA1NUBAP0BAQEBAQEBAQEBAQP/CABEIACgAIwMBIgACEQEDEQH/
+xACyAAEAAwEBAAAAAAAAAAAAAAAAAQMFBAIBAAMBAQAAAAAAAAAAAAAAAAABAgMEEAABBAECBgMB
+AAAAAAAAAAABAAIDBAUREyESIhQVRRAjNQYRAAEDAgMDBwgLAAAAAAAAAAERAgMAEiETBDFRIkFh
+cUIUhcWhMlKDs9M01GKSIzNzwyRkhJQVEgABAwAGCQUAAAAAAAAAAAAAARECIUFRYYESEDFxodEy
+QoKicjNDNIT/2gAMAwEAAhEDEQAAANWbtDvwx51+eStam/NlFqUEyBo7eck8dAFoyr//2gAIAQIA
+AQUAe+RpEry7QJ4POA4FDeR3V9q//9oACAEDAAEFAAAUWjRDTTh8dC6V0r//2gAIAQEAAQUAiihn
+hNOsu1rBMr0HLks9hi7UdSE5ytyx5evM/J6dx6rCcInfp3ON2+CZfVY1z46RMm9IHyveZpW+q8Vc
+rtkr51qbF/QEx0s04eCg2//aAAgBAgIGPwBoxWSbRIyisXvNSfZsJrbFYkFblRlP1FOTHK+J8fid
+XuefE//aAAgBAwIGPwCmTDpJzsE9Tkrzs0VlXLuP/9oACAEBAQY/AIZ54GanU6mJk8sksTZnEyNa
+9Be11rW3IAMK+ChH8SP3dY6KH+pH7uvhNORy2wxscOhzGtc01n9rmvzP85bup2zs2b+JZ1q0csgJ
+adHC3hRVMcR5SN1XWSIqLw7fr0GNa8KQ242oC7Aea4moCAFc14cd4FqV3n4lWjP7KL2cNXfT/Kph
+3ZPtDUL0Nrbmk7i61K7z8SrQyNAd+khaQSm2OPmO6s5G3XXWrgllm1KLyjCjbUN2LSXLsFBha0BW
+kkOJ80g7Leau8vEqy9HPEdO0/ZRTxOe6NvoNeyWPhHIow31wt0rvVS/MVjHph6qX5iuKXTQr1mwv
+Lhzi+dwXpFZWdNZk5SX9fMzs/Z95fivkr//Z" transform="matrix(0.9999 0 0 0.9999 7.0146 6.0142)">
+</image>
+</svg>
diff --git a/pidgin/plugins/disco/gtkdisco.c b/pidgin/plugins/disco/gtkdisco.c
index 92b078b77b..320b49541e 100644
--- a/pidgin/plugins/disco/gtkdisco.c
+++ b/pidgin/plugins/disco/gtkdisco.c
@@ -162,12 +162,15 @@ static void register_button_cb(GtkWidget *unused, PidginDiscoDialog *dialog)
static void discolist_cancel_cb(PidginDiscoList *pdl, const char *server)
{
+ pdl->dialog->prompt_handle = NULL;
+
pidgin_disco_list_set_in_progress(pdl, FALSE);
pidgin_disco_list_unref(pdl);
}
static void discolist_ok_cb(PidginDiscoList *pdl, const char *server)
{
+ pdl->dialog->prompt_handle = NULL;
gtk_widget_set_sensitive(pdl->dialog->browse_button, TRUE);
if (!server || !*server) {
@@ -236,7 +239,7 @@ static void browse_button_cb(GtkWidget *button, PidginDiscoDialog *dialog)
/* Note to translators: The string "Enter an XMPP Server" is asking the
user to type the name of an XMPP server which will then be queried */
- purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"),
+ dialog->prompt_handle = purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"),
_("Select an XMPP server to query"),
server, FALSE, FALSE, NULL,
_("Find Services"), PURPLE_CALLBACK(discolist_ok_cb),
@@ -390,6 +393,9 @@ destroy_win_cb(GtkWidget *window, gpointer d)
PidginDiscoDialog *dialog = d;
PidginDiscoList *list = dialog->discolist;
+ if (dialog->prompt_handle)
+ purple_request_close(PURPLE_REQUEST_INPUT, dialog->prompt_handle);
+
if (list) {
list->dialog = NULL;
diff --git a/pidgin/plugins/disco/gtkdisco.h b/pidgin/plugins/disco/gtkdisco.h
index 8f2fa5b20b..0c1aaba73a 100644
--- a/pidgin/plugins/disco/gtkdisco.h
+++ b/pidgin/plugins/disco/gtkdisco.h
@@ -43,6 +43,8 @@ struct _PidginDiscoDialog {
PurpleAccount *account;
PidginDiscoList *discolist;
+
+ gpointer *prompt_handle;
};
struct _PidginDiscoList {
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7729e2d457..6533d3ec21 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -126,6 +126,15 @@ libpurple/protocols/msnp9/session.c
libpurple/protocols/msnp9/state.c
libpurple/protocols/msnp9/switchboard.c
libpurple/protocols/msnp9/userlist.c
+libpurple/protocols/mxit/actions.c
+libpurple/protocols/mxit/filexfer.c
+libpurple/protocols/mxit/http.c
+libpurple/protocols/mxit/login.c
+libpurple/protocols/mxit/mxit.c
+libpurple/protocols/mxit/profile.c
+libpurple/protocols/mxit/protocol.c
+libpurple/protocols/mxit/roster.c
+libpurple/protocols/mxit/splashscreen.c
libpurple/protocols/myspace/myspace.c
libpurple/protocols/myspace/user.c
libpurple/protocols/myspace/zap.c