# HG changeset patch # User Yoshiki Yazawa # Date 1258092250 -32400 # Node ID 0399f8ef665ae3d93116200ae838848141df19a6 # Parent 08a52bdd9619673c753501051905e40d1166002d# Parent 738cd1adb3cf18f159e7495776597b4626dbd5d3 merged with im.pidgin.pidgin diff -r 08a52bdd9619 -r 0399f8ef665a COPYRIGHT --- a/COPYRIGHT Sun Nov 08 02:21:28 2009 +0900 +++ b/COPYRIGHT Fri Nov 13 15:04:10 2009 +0900 @@ -276,6 +276,7 @@ Lokheed Norberto Lopes Shlomi Loubaton +Pieter Loubser Brian Lu Uli Luckas Matthew Luckie @@ -319,6 +320,7 @@ Sergio Moretto Andrei Mozzhuhin Christian Muise +MXit Lifestyle (Pty) Ltd. Richard Nelson Dennis Nezic Matthew A. Nicholson @@ -497,6 +499,7 @@ James Vega David Vermeille Sid Vicious +Andrew Victor Jorge VillaseƱor (Masca) Bjoern Voigt Wan Hing Wah diff -r 08a52bdd9619 -r 0399f8ef665a ChangeLog --- a/ChangeLog Sun Nov 08 02:21:28 2009 +0900 +++ b/ChangeLog Fri Nov 13 15:04:10 2009 +0900 @@ -4,6 +4,8 @@ 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') @@ -40,6 +42,8 @@ account's domain, and use that for voice and video if found and the user didn't set one manually in prefs. * Fix a crash when adding a buddy without an '@'. + * Don't show the option to send a file to a buddy if we know for certain + they don't support any file transfer method supported by libpurple. Yahoo: * Fix sending /buzz. diff -r 08a52bdd9619 -r 0399f8ef665a configure.ac --- a/configure.ac Sun Nov 08 02:21:28 2009 +0900 +++ b/configure.ac Fri Nov 13 15:04:10 2009 +0900 @@ -1078,7 +1078,7 @@ 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 @@ 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_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_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 @@ 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 @@ 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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/media.c --- a/libpurple/media.c Sun Nov 08 02:21:28 2009 +0900 +++ b/libpurple/media.c Fri Nov 13 15:04:10 2009 +0900 @@ -2281,7 +2281,8 @@ stream->session->type), NULL); stream->accepted = TRUE; - if (stream->remote_candidates != NULL) { + if (stream->remote_candidates != NULL && + stream->initiator == FALSE) { GError *err = NULL; fs_stream_set_remote_candidates(stream->stream, stream->remote_candidates, &err); @@ -2816,14 +2817,16 @@ } fsstream = fs_session_new_stream(session->session, - participant, type_direction & - FS_DIRECTION_RECV, transmitter, + participant, initiator == TRUE ? + type_direction : (type_direction & + FS_DIRECTION_RECV), transmitter, new_num_params, param, &err); g_free(param); } else { fsstream = fs_session_new_stream(session->session, - participant, type_direction & - FS_DIRECTION_RECV, transmitter, + participant, initiator == TRUE ? + type_direction : (type_direction & + FS_DIRECTION_RECV), transmitter, num_params, params, &err); } @@ -2952,7 +2955,7 @@ stream->remote_candidates = g_list_concat(stream->remote_candidates, purple_media_candidate_list_to_fs(remote_candidates)); - if (stream->accepted == TRUE) { + if (stream->initiator == TRUE || stream->accepted == TRUE) { fs_stream_set_remote_candidates(stream->stream, stream->remote_candidates, &err); diff -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/Makefile.am --- a/libpurple/protocols/Makefile.am Sun Nov 08 02:21:28 2009 +0900 +++ b/libpurple/protocols/Makefile.am Fri Nov 13 15:04:10 2009 +0900 @@ -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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/jabber/buddy.c --- a/libpurple/protocols/jabber/buddy.c Sun Nov 08 02:21:28 2009 +0900 +++ b/libpurple/protocols/jabber/buddy.c Fri Nov 13 15:04:10 2009 +0900 @@ -2341,6 +2341,12 @@ } gboolean +jabber_resource_know_capabilities(const JabberBuddyResource *jbr) +{ + return jbr->caps.info != NULL; +} + +gboolean jabber_resource_has_capability(const JabberBuddyResource *jbr, const gchar *cap) { const GList *node = NULL; diff -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/jabber/buddy.h --- a/libpurple/protocols/jabber/buddy.h Sun Nov 08 02:21:28 2009 +0900 +++ b/libpurple/protocols/jabber/buddy.h Fri Nov 13 15:04:10 2009 +0900 @@ -123,6 +123,7 @@ void jabber_vcard_fetch_mine(JabberStream *js); +gboolean jabber_resource_know_capabilities(const JabberBuddyResource *jbr); gboolean jabber_resource_has_capability(const JabberBuddyResource *jbr, const gchar *cap); gboolean jabber_buddy_has_capability(const JabberBuddy *jb, const gchar *cap); diff -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Sun Nov 08 02:21:28 2009 +0900 +++ b/libpurple/protocols/jabber/jabber.c Fri Nov 13 15:04:10 2009 +0900 @@ -52,6 +52,7 @@ #include "data.h" #include "disco.h" #include "google.h" +#include "ibb.h" #include "iq.h" #include "jutil.h" #include "message.h" @@ -3227,6 +3228,50 @@ #endif } +gboolean jabber_can_receive_file(PurpleConnection *gc, const char *who) +{ + JabberStream *js = gc->proto_data; + + if (js) { + JabberBuddy *jb = jabber_buddy_find(js, who, FALSE); + GList *iter; + gboolean has_resources_without_caps = FALSE; + + /* find out if there is any resources without caps */ + for (iter = jb->resources; iter ; iter = g_list_next(iter)) { + JabberBuddyResource *jbr = (JabberBuddyResource *) iter->data; + + if (!jabber_resource_know_capabilities(jbr)) { + has_resources_without_caps = TRUE; + } + } + + if (has_resources_without_caps) { + /* there is at least one resource which we don't have caps for, + let's assume they can receive files... */ + return TRUE; + } else { + /* we have caps for all the resources, see if at least one has + right caps */ + for (iter = jb->resources; iter ; iter = g_list_next(iter)) { + JabberBuddyResource *jbr = (JabberBuddyResource *) iter->data; + + if (jabber_resource_has_capability(jbr, + "http://jabber.org/protocol/si/profile/file-transfer") + && (jabber_resource_has_capability(jbr, + "http://jabber.org/protocol/bytestreams") + || jabber_resource_has_capability(jbr, + XEP_0047_NAMESPACE))) { + return TRUE; + } + } + return FALSE; + } + } else { + return TRUE; + } +} + void jabber_register_commands(void) { PurpleCmdId id; diff -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/jabber/jabber.h --- a/libpurple/protocols/jabber/jabber.h Sun Nov 08 02:21:28 2009 +0900 +++ b/libpurple/protocols/jabber/jabber.h Fri Nov 13 15:04:10 2009 +0900 @@ -368,6 +368,7 @@ gboolean jabber_initiate_media(PurpleAccount *account, const char *who, PurpleMediaSessionType type); PurpleMediaCaps jabber_get_media_caps(PurpleAccount *account, const char *who); +gboolean jabber_can_receive_file(PurpleConnection *gc, const gchar *who); void jabber_register_commands(void); void jabber_unregister_commands(void); diff -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/jabber/jingle/rtp.c --- a/libpurple/protocols/jabber/jingle/rtp.c Sun Nov 08 02:21:28 2009 +0900 +++ b/libpurple/protocols/jabber/jingle/rtp.c Fri Nov 13 15:04:10 2009 +0900 @@ -823,6 +823,64 @@ g_object_unref(session); break; } + case JINGLE_DESCRIPTION_INFO: { + JingleSession *session = + jingle_content_get_session(content); + xmlnode *description = xmlnode_get_child( + xmlcontent, "description"); + GList *codecs, *iter, *iter2, *remote_codecs = + jingle_rtp_parse_codecs(description); + gchar *name = jingle_content_get_name(content); + gchar *remote_jid = + jingle_session_get_remote_jid(session); + PurpleMedia *media; + + media = jingle_rtp_get_media(session); + + /* + * This may have problems if description-info is + * received without the optional parameters for a + * codec with configuration info (such as THEORA + * or H264). The local configuration info may be + * set for the remote codec. + * + * As of 2.6.3 there's no API to support getting + * the remote codecs specifically, just the + * intersection. Another option may be to cache + * the remote codecs received in initiate/accept. + */ + codecs = purple_media_get_codecs(media, name); + + for (iter = codecs; iter; iter = g_list_next(iter)) { + guint id; + + id = purple_media_codec_get_id(iter->data); + iter2 = remote_codecs; + + for (; iter2; iter2 = g_list_next(iter2)) { + if (purple_media_codec_get_id( + iter2->data) != id) + continue; + + g_object_unref(iter->data); + iter->data = iter2->data; + remote_codecs = g_list_delete_link( + remote_codecs, iter2); + break; + } + } + + codecs = g_list_concat(codecs, remote_codecs); + + purple_media_set_remote_codecs(media, + name, remote_jid, codecs); + + purple_media_codec_list_free (codecs); + g_free(remote_jid); + g_free(name); + g_object_unref(session); + break; + } default: break; } diff -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Sun Nov 08 02:21:28 2009 +0900 +++ b/libpurple/protocols/jabber/libxmpp.c Fri Nov 13 15:04:10 2009 +0900 @@ -111,7 +111,7 @@ jabber_roomlist_get_list, /* roomlist_get_list */ jabber_roomlist_cancel, /* roomlist_cancel */ NULL, /* roomlist_expand_category */ - NULL, /* can_receive_file */ + jabber_can_receive_file, /* can_receive_file */ jabber_si_xfer_send, /* send_file */ jabber_si_new_xfer, /* new_xfer */ jabber_offline_message, /* offline_message */ diff -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/Makefile.am Fri Nov 13 15:04:10 2009 +0900 @@ -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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/Makefile.mingw --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/Makefile.mingw Fri Nov 13 15:04:10 2009 +0900 @@ -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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/actions.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/actions.c Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,437 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- handle MXit plugin actions -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 +#include +#include +#include + +#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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/actions.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/actions.h Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,34 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- handle MXit plugin actions -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/aes.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/aes.c Fri Nov 13 15:04:10 2009 +0900 @@ -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 +#include + +#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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/aes.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/aes.h Fri Nov 13 15:04:10 2009 +0900 @@ -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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/chunk.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/chunk.c Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,659 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- handle chunked data (multimedia messages) -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 +#include +#include + +#include "purple.h" +#include "protocol.h" +#include "mxit.h" +#include "chunk.h" +#include "filexfer.h" + + +/*======================================================================================================================== + * Data-Type encoding + */ + +#if 0 +#include +#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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/chunk.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/chunk.h Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,140 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- handle chunked data (multimedia messages) -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/cipher.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/cipher.c Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,111 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user password encryption -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 +#include +#include + +#include "purple.h" + +#include "mxit.h" +#include "cipher.h" +#include "aes.h" + + +/* password encryption */ +#define INITIAL_KEY "6170383452343567" +#define SECRET_HEADER "" + + +/*------------------------------------------------------------------------ + * 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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/cipher.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/cipher.h Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,36 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user password encryption -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/filexfer.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/filexfer.c Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,454 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- file transfers (sending and receiving) -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 +#include +#include + +#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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/filexfer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/filexfer.h Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,50 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- file transfers (sending and receiving) -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/formcmds.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/formcmds.c Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,397 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit Forms & Commands -- + * + * Andrew Victor + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 +#include +#include + +#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 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 map. + * + * @param cmd The MXit command string + * @return The 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 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 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, "%s", 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 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), "", 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 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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/formcmds.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/formcmds.h Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,35 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit Forms & Commands -- + * + * Andrew Victor + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/http.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/http.c Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,331 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit client protocol implementation -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 +#include +#include +#include + +#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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/http.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/http.h Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,47 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit client protocol implementation -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/login.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/login.c Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,789 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit user login functionality -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 +#include + +#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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/login.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/login.h Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,45 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit user login functionality -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/markup.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/markup.c Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,1192 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- convert between MXit and libPurple markup -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 +#include +#include + +#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, "%s", link, displayname ); +#else + g_string_append_printf( mx->msg, "%s", 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 = "continuing...\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 ); + 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, "%s Vibe...", 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, "%s: ", 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, "" ); + else + g_string_append( mx->msg, "" ); + 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, "" ); + else + g_string_append( mx->msg, "" ); + 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, "" ); + else + g_string_append( mx->msg, "" ); + 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, "" ); + i += COLORCODE_LEN; + } + else if ( strspn( tmpstr1, "0123456789abcdefABCDEF") == COLORCODE_LEN ) { + /* definitely a numeric colour code */ + g_string_append_printf( mx->msg, "", 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, "" ); + i++; + break; + case '-' : + /* decrement text size */ + g_string_append( mx->msg, "" ); + i++; + break; + case '{' : + /* custom emoticon */ + if ( i + 2 >= len ) { + /* message too short */ + g_string_append_c( mx->msg, '.' ); + break; + } + + parse_emoticon_str( &message[i+2], tmpstr1 ); + if ( tmpstr1[0] != '\0' ) { + mx->got_img = TRUE; + + if ( g_hash_table_lookup( mx->session->iimages, tmpstr1 ) ) { + /* emoticon found in the cache, so we do not have to request it from the WAPsite */ + } + else { + /* request emoticon from the WAPsite */ + mx->img_count++; + emoticon_request( mx, tmpstr1 ); + } + + g_string_append_printf( mx->msg, MXIT_II_TAG"%s>", tmpstr1 ); + i += strlen( tmpstr1 ) + 2; + } + else + g_string_append_c( mx->msg, '.' ); + + break; + default : + g_string_append_c( mx->msg, '.' ); + break; + } + break; + case '\\' : + if ( i + 1 >= len ) { + /* message too short for an escaped character */ + g_string_append_c( mx->msg, '\\' ); + } + else { + /* ignore the next character, because its been escaped */ + g_string_append_c( mx->msg, message[i + 1] ); + i++; + } + break; + + + /* command parsing */ + case ':' : + if ( i + 1 >= len ) { + /* message too short */ + g_string_append_c( mx->msg, ':' ); + break; + } + + if ( message[i+1] == '@' ) { + /* this is a vibe! */ + int size; + + if ( i + 2 >= len ) { + /* message too short */ + g_string_append_c( mx->msg, message[i] ); + break; + } + + size = mxit_parse_vibe( mx, &message[i] ); + if ( size == 0 ) + g_string_append_c( mx->msg, message[i] ); + else + i += size; + } + else if ( msgtype != CP_MSGTYPE_COMMAND ) { + /* this is not a command message */ + g_string_append_c( mx->msg, message[i] ); + } + else if ( message[i+1] == ':' ) { + /* parse out the command */ + int size; + + size = mxit_parse_command( mx, &message[i] ); + if ( size == 0 ) + g_string_append_c( mx->msg, ':' ); + else + i += size; + } + else { + g_string_append_c( mx->msg, ':' ); + } + break; + + + /* these aren't MXit markup, but are interpreted by libPurple */ + case '<' : + g_string_append( mx->msg, "<" ); + break; + case '>' : + g_string_append( mx->msg, ">" ); + break; + case '&' : + g_string_append( mx->msg, "&" ); + break; + case '"' : + g_string_append( mx->msg, """ ); + break; + + default : + /* text */ + g_string_append_c( mx->msg, message[i] ); + break; + } + } +} + + +/*------------------------------------------------------------------------ + * Insert an inline image command. + * + * @param mx The message text as processed so far. + * @oaram id The imgstore ID of the inline image. + */ +static void inline_image_add( GString* mx, int id ) +{ + PurpleStoredImage *image; + gconstpointer img_data; + gsize img_size; + gchar* enc; + + image = purple_imgstore_find_by_id( id ); + if ( image == NULL ) + return; + + img_data = purple_imgstore_get_data( image ); + img_size = purple_imgstore_get_size( image ); + + enc = purple_base64_encode( img_data, img_size ); + + g_string_append( mx, "::op=img|dat=" ); + g_string_append( mx, enc ); + g_string_append_c( mx, ':' ); + + g_free( enc ); +} + + +/*------------------------------------------------------------------------ + * Convert libpurple (HTML) markup to MXit protocol markup (for sending to MXit). + * Any MXit markup codes in the original message also need to be escaped. + * + * @param message The message text containing libPurple (HTML) markup + * @return The message text containing MXit markup + */ +char* mxit_convert_markup_tx( const char* message, int* msgtype ) +{ + GString* mx; + struct tag* tag; + GList* entry; + GList* tagstack = NULL; + char* reply; + char color[8]; + int len = strlen ( message ); + int i; + +#ifdef MXIT_DEBUG_MARKUP + purple_debug_info( MXIT_PLUGIN_ID, "Markup TX (original): '%s'\n", message ); +#endif + + /* + * libPurple uses the following HTML markup codes: + * Bold: ... + * Italics: ... + * Underline: ... + * Strikethrough: ... (NO MXIT SUPPORT) + * Font size: ... + * Font type: ... (NO MXIT SUPPORT) + * Font colour: ... + * Links: ... + * Newline:
+ * Inline image: + * The following characters are also encoded: + * & " < > + */ + + /* new message data */ + mx = g_string_sized_new( len ); + + /* run through the message and check for HTML markup */ + for ( i = 0; i < len; i++ ) { + + switch ( message[i] ) { + case '<' : + if ( purple_str_has_prefix( &message[i], "" ) || purple_str_has_prefix( &message[i], "" ) ) { + /* bold */ + g_string_append_c( mx, '*' ); + } + else if ( purple_str_has_prefix( &message[i], "" ) || purple_str_has_prefix( &message[i], "" ) ) { + /* italics */ + g_string_append_c( mx, '/' ); + } + else if ( purple_str_has_prefix( &message[i], "" ) || purple_str_has_prefix( &message[i], "" ) ) { + /* underline */ + g_string_append_c( mx, '_' ); + } + else if ( purple_str_has_prefix( &message[i], "
" ) ) { + /* newline */ + g_string_append_c( mx, '\n' ); + } + else if ( purple_str_has_prefix( &message[i], "" ) ) { + /* 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], "') */ + 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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/markup.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/markup.h Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,40 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- convert between MXit and libPurple markup -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 " + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 +#include + +#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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/multimx.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/multimx.h Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,104 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MultiMx GroupChat -- + * + * Andrew Victor + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/mxit.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/mxit.c Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,694 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit libPurple plugin API -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 +#include +#include + +#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, "Loading menu...\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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/mxit.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/mxit.h Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,197 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit libPurple plugin API -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 +#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 +#include +#include +#include +#include +#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 " +#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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/profile.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/profile.c Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,160 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user profile's -- + * + * Andrew Victor + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 +#include + +#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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/profile.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/profile.h Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,56 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user profile's -- + * + * Andrew Victor + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 + + +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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/protocol.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/protocol.c Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,2442 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit client protocol implementation -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 +#include +#include +#include + +#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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/protocol.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/protocol.h Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,304 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit client protocol implementation -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/roster.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/roster.c Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,722 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user roster management (mxit contacts) -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 +#include +#include + +#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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/roster.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/roster.h Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,139 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user roster management (mxit contacts) -- + * + * Pieter Loubser + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/splashscreen.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/splashscreen.c Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,223 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- splash screens -- + * + * Andrew Victor + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 +#include + +#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), "", 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 -r 08a52bdd9619 -r 0399f8ef665a libpurple/protocols/mxit/splashscreen.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/splashscreen.h Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,59 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- splash screens -- + * + * Andrew Victor + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * + * + * 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 -r 08a52bdd9619 -r 0399f8ef665a pidgin/gtkprefs.c --- a/pidgin/gtkprefs.c Sun Nov 08 02:21:28 2009 +0900 +++ b/pidgin/gtkprefs.c Fri Nov 13 15:04:10 2009 +0900 @@ -1134,7 +1134,7 @@ 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 -r 08a52bdd9619 -r 0399f8ef665a pidgin/pixmaps/Makefile.am --- a/pidgin/pixmaps/Makefile.am Sun Nov 08 02:21:28 2009 +0900 +++ b/pidgin/pixmaps/Makefile.am Fri Nov 13 15:04:10 2009 +0900 @@ -236,6 +236,7 @@ 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/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/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 -r 08a52bdd9619 -r 0399f8ef665a pidgin/pixmaps/protocols/16/mxit.png Binary file pidgin/pixmaps/protocols/16/mxit.png has changed diff -r 08a52bdd9619 -r 0399f8ef665a pidgin/pixmaps/protocols/22/mxit.png Binary file pidgin/pixmaps/protocols/22/mxit.png has changed diff -r 08a52bdd9619 -r 0399f8ef665a pidgin/pixmaps/protocols/48/mxit.png Binary file pidgin/pixmaps/protocols/48/mxit.png has changed diff -r 08a52bdd9619 -r 0399f8ef665a pidgin/pixmaps/protocols/scalable/mxit.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/pixmaps/protocols/scalable/mxit.svg Fri Nov 13 15:04:10 2009 +0900 @@ -0,0 +1,24 @@ + + + + + + + diff -r 08a52bdd9619 -r 0399f8ef665a po/POTFILES.in --- a/po/POTFILES.in Sun Nov 08 02:21:28 2009 +0900 +++ b/po/POTFILES.in Fri Nov 13 15:04:10 2009 +0900 @@ -126,6 +126,15 @@ 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