# HG changeset patch # User Paul Aurich # Date 1257730944 0 # Node ID 585d6f844f79bc8444335927d30726c258e0660b # Parent 231af7ce1b49543ad938abc0add4e5077e7b0d6a# Parent 994e8d214754e71bdc3c170f26c424e2a7ebd261 propagate from branch 'im.pidgin.pidgin' (head 37329f033a30f4f4f5048f9fdc86df4400e3ada5) to branch 'im.pidgin.soc.2009.webkitmessageview' (head 7be18cb8139c12d8080e3e0603264c2a623a15d3) diff -r 994e8d214754 -r 585d6f844f79 COPYRIGHT --- a/COPYRIGHT Sun Nov 08 01:12:44 2009 +0000 +++ b/COPYRIGHT Mon Nov 09 01:42:24 2009 +0000 @@ -268,6 +268,7 @@ Ambrose C. Li Nicolas Lichtmaier Wesley Lin +Shaun Lindsay Artem Litvinovich Josh Littlefield Daniel Ljungborg @@ -275,6 +276,7 @@ Lokheed Norberto Lopes Shlomi Loubaton +Pieter Loubser Brian Lu Uli Luckas Matthew Luckie @@ -318,6 +320,7 @@ Sergio Moretto Andrei Mozzhuhin Christian Muise +MXit Lifestyle (Pty) Ltd. Richard Nelson Dennis Nezic Matthew A. Nicholson @@ -406,11 +409,10 @@ Carsten Schaar Toby Schaffer Jonathan Schleifer -Matteo Settenvini -Colin Seymour Luke Schierer Ralph Schmieder David Schmitt +Heiko Schmitt Mark Schneider Evan Schoenberg Gabriel Schulhof @@ -420,6 +422,8 @@ Peter Seebach Don Seiler Leonardo Serra +Matteo Settenvini +Colin Seymour Jim Seymour Javeed Shaikh Joe Shaw @@ -495,6 +499,7 @@ James Vega David Vermeille Sid Vicious +Andrew Victor Jorge Villaseñor (Masca) Bjoern Voigt Wan Hing Wah diff -r 994e8d214754 -r 585d6f844f79 ChangeLog --- a/ChangeLog Sun Nov 08 01:12:44 2009 +0000 +++ b/ChangeLog Mon Nov 09 01:42:24 2009 +0000 @@ -1,13 +1,34 @@ + Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul -version 2.6.3 (??/??/20??): +version 2.6.4 (??/??/20??): + libpurple: + * Actually emit the hold signal for media calls. + General: * New 'plugins' sub-command to 'debug' command (i.e. '/debug plugins') to announce the list of loaded plugins (in both Finch and Pidgin). - * Fix a crash when performing DNS queries on Unixes that use the - blocking DNS lookups. (Brian Lu) * Fix building the GnuTLS plugin with older versions of GnuTLS. * Fix DNS TXT query resolution. + * Always rejoin open chats after an account reconnects. + + AIM and ICQ: + * Better rate limit calculations and other improvements. (Aman Gupta) + * More detailed error messages when messages fail to send. (Aman Gupta) + * The simultaneous login account option is respected when using + the clientLogin authentication method. + * Fix offline message retrieval (broken in 2.6.3) + * Fix SSL when clientLogin is enabled. + + MSN: + * Don't forget display names for buddies. + * Fix a random crash that might occur when idle. + * Fix more FQY 240 connection errors. + * Fix a crash that could occur when adding a buddy. + * Fix an occasional crash when sending message to an offline user. + * Fix a random crash that might occur when idle. + * Fix a crash when logging in with some long non-ASCII passwords. + (Shaun Lindsay) XMPP: * Users connecting to Google Talk now have an "Initiate Chat" context menu @@ -15,6 +36,9 @@ * Fix a crash when attempting to validate an invalid JID. * Resolve an issue when connecting to iChat Server when no resource is specified. + * Try to automatically find a STUN server by using an SRV lookup on the + 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 '@'. Yahoo: @@ -32,7 +56,18 @@ Pidgin: * The userlist in a multiuser chat can be styled via gtkrc by using the - widget name "pidgin_conv_userlist". + widget name "pidgin_conv_userlist". (Heiko Schmitt) + * Add a hold button to the media window. + +version 2.6.3 (10/16/2009): + General: + * Fix a crash when performing DNS queries on Unixes that use the + blocking DNS lookups. (Brian Lu) + + AIM and ICQ: + * Fix a crash when some clients send contacts in a format we don't + understand. + * Fix blocking and other privacy lists. (Thanks to AOL) version 2.6.2 (09/05/2009): libpurple: diff -r 994e8d214754 -r 585d6f844f79 ChangeLog.API --- a/ChangeLog.API Sun Nov 08 01:12:44 2009 +0000 +++ b/ChangeLog.API Mon Nov 09 01:42:24 2009 +0000 @@ -1,5 +1,8 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.6.3 (10/16/2009): + No changes + version 2.6.2 (09/05/2009): Perl: Added: diff -r 994e8d214754 -r 585d6f844f79 ChangeLog.win32 --- a/ChangeLog.win32 Sun Nov 08 01:12:44 2009 +0000 +++ b/ChangeLog.win32 Mon Nov 09 01:42:24 2009 +0000 @@ -1,3 +1,6 @@ +version 2.6.3 (10/16/2009): + * No changes + version 2.6.2 (09/05/2009): * No changes diff -r 994e8d214754 -r 585d6f844f79 NEWS --- a/NEWS Sun Nov 08 01:12:44 2009 +0000 +++ b/NEWS Mon Nov 09 01:42:24 2009 +0000 @@ -2,6 +2,11 @@ Our development blog is available at: http://planet.pidgin.im +2.6.3 (10/16/2009): + Mark: Someone reported a fairly serious bug in our AIM/ICQ code + so we're releasing a special "severe bug fix only" build. See the + ChangeLog for details. Enjoy! + 2.6.2 (09/05/2009): Mark: Woo boy it's been a busy two weeks. There was a lot of new code in 2.6.0, and with new code comes new bugs. The cadre of relentless diff -r 994e8d214754 -r 585d6f844f79 autogen.sh --- a/autogen.sh Sun Nov 08 01:12:44 2009 +0000 +++ b/autogen.sh Mon Nov 09 01:42:24 2009 +0000 @@ -83,7 +83,7 @@ OUTPUT=`mktemp autogen-XXXXXX` - printf "%s" "running ${CMD} ${@}... " + printf "running %s %s... " ${CMD} "$*" ${CMD} ${@} >${OUTPUT} 2>&1 if [ $? != 0 ] ; then @@ -99,9 +99,17 @@ fi } +cleanup () { + rm -f autogen-?????? + echo + exit 2 +} + ############################################################################### # We really start here, yes, very sneaky! ############################################################################### +trap cleanup 2 + FIGLET=`which figlet 2> /dev/null` if [ x"${FIGLET}" != x"" ] ; then ${FIGLET} -f small ${PACKAGE} @@ -143,7 +151,7 @@ run_or_die ${INTLTOOLIZE} ${INTLTOOLIZE_FLAGS:-"-c -f --automake"} # This call to sed is needed to work around an annoying bug in intltool 0.40.6 # See http://developer.pidgin.im/ticket/9520 for details -run_or_die ${SED} "s:'\^\$\$lang\$\$':\^\$\$lang\$\$:g" -i po/Makefile.in.in +run_or_die ${SED} -i.bak -e "s:'\^\$\$lang\$\$':\^\$\$lang\$\$:g" po/Makefile.in.in run_or_die ${ACLOCAL} ${ACLOCAL_FLAGS:-"-I m4macros"} run_or_die ${AUTOHEADER} ${AUTOHEADER_FLAGS} run_or_die ${AUTOMAKE} ${AUTOMAKE_FLAGS:-"-a -c --gnu"} diff -r 994e8d214754 -r 585d6f844f79 configure.ac --- a/configure.ac Sun Nov 08 01:12:44 2009 +0000 +++ b/configure.ac Mon Nov 09 01:42:24 2009 +0000 @@ -46,7 +46,7 @@ m4_define([purple_lt_current], [6]) m4_define([purple_major_version], [2]) m4_define([purple_minor_version], [6]) -m4_define([purple_micro_version], [3]) +m4_define([purple_micro_version], [4]) m4_define([purple_version_suffix], [devel]) m4_define([purple_version], [purple_major_version.purple_minor_version.purple_micro_version]) @@ -55,7 +55,7 @@ m4_define([gnt_lt_current], [6]) m4_define([gnt_major_version], [2]) m4_define([gnt_minor_version], [6]) -m4_define([gnt_micro_version], [3]) +m4_define([gnt_micro_version], [4]) m4_define([gnt_version_suffix], [devel]) m4_define([gnt_version], [gnt_major_version.gnt_minor_version.gnt_micro_version]) @@ -1080,7 +1080,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//'` @@ -1137,6 +1137,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 ;; @@ -1157,6 +1158,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") @@ -1171,7 +1173,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//'` @@ -1198,6 +1200,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 ;; @@ -2532,6 +2535,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 994e8d214754 -r 585d6f844f79 finch/gntconn.c --- a/finch/gntconn.c Sun Nov 08 01:12:44 2009 +0000 +++ b/finch/gntconn.c Mon Nov 09 01:42:24 2009 +0000 @@ -107,7 +107,6 @@ { FinchAutoRecon *info; PurpleAccount *account = purple_connection_get_account(gc); - GList *list; if (!purple_connection_error_is_fatal(reason)) { info = g_hash_table_lookup(hash, account); @@ -144,21 +143,6 @@ g_free(secondary); purple_account_set_enabled(account, FINCH_UI, FALSE); } - - /* If we have any open chats, we probably want to rejoin when we get back online. */ - list = purple_get_chats(); - while (list) { - PurpleConversation *conv = list->data; - list = list->next; - if (purple_conversation_get_account(conv) != account || - purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) - continue; - purple_conversation_set_data(conv, "want-to-rejoin", GINT_TO_POINTER(TRUE)); - purple_conversation_write(conv, NULL, _("The account has disconnected and you are no " - "longer in this chat. You will be automatically rejoined in the chat when " - "the account reconnects."), - PURPLE_MESSAGE_SYSTEM, time(NULL)); - } } static void diff -r 994e8d214754 -r 585d6f844f79 finch/gntconv.c --- a/finch/gntconv.c Sun Nov 08 01:12:44 2009 +0000 +++ b/finch/gntconv.c Mon Nov 09 01:42:24 2009 +0000 @@ -367,6 +367,28 @@ } } +static void +account_signing_off(PurpleConnection *gc) +{ + GList *list = purple_get_chats(); + PurpleAccount *account = purple_connection_get_account(gc); + + /* We are about to sign off. See which chats we are currently in, and mark + * them for rejoin on reconnect. */ + while (list) { + PurpleConversation *conv = list->data; + if (!purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)) && + purple_conversation_get_account(conv) == account) { + purple_conversation_set_data(conv, "want-to-rejoin", GINT_TO_POINTER(TRUE)); + purple_conversation_write(conv, NULL, _("The account has disconnected and you are no " + "longer in this chat. You will be automatically rejoined in the chat when " + "the account reconnects."), + PURPLE_MESSAGE_SYSTEM, time(NULL)); + } + list = list->next; + } +} + static gpointer finch_conv_get_handle(void) { @@ -642,8 +664,25 @@ create_conv_from_userlist(GntWidget *widget, FinchConv *fc) { PurpleAccount *account = purple_conversation_get_account(fc->active_conv); - char *name = gnt_tree_get_selection_data(GNT_TREE(widget)); - purple_conversation_new(PURPLE_CONV_TYPE_IM, account, name); + PurpleConnection *gc = purple_account_get_connection(account); + PurplePluginProtocolInfo *prpl_info = NULL; + char *name, *realname; + + if (!gc) { + purple_conversation_write(fc->active_conv, NULL, _("You are not connected."), + PURPLE_MESSAGE_SYSTEM, time(NULL)); + return; + } + + name = gnt_tree_get_selection_data(GNT_TREE(widget)); + + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); + if (prpl_info && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_cb_real_name)) + realname = prpl_info->get_cb_real_name(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(fc->active_conv)), name); + else + realname = NULL; + purple_conversation_new(PURPLE_CONV_TYPE_IM, account, realname ? realname : name); + g_free(realname); } static void @@ -1433,6 +1472,8 @@ PURPLE_CALLBACK(account_signed_on_off), NULL); purple_signal_connect(purple_connections_get_handle(), "signed-off", finch_conv_get_handle(), PURPLE_CALLBACK(account_signed_on_off), NULL); + purple_signal_connect(purple_connections_get_handle(), "signing-off", finch_conv_get_handle(), + PURPLE_CALLBACK(account_signing_off), NULL); } void finch_conversation_uninit() diff -r 994e8d214754 -r 585d6f844f79 finch/libgnt/gntentry.c --- a/finch/libgnt/gntentry.c Sun Nov 08 01:12:44 2009 +0000 +++ b/finch/libgnt/gntentry.c Mon Nov 09 01:42:24 2009 +0000 @@ -1044,8 +1044,11 @@ snprintf(entry->start, len + 1, "%s", text); entry->end = entry->start + len; - entry->scroll = entry->start + scroll; - entry->cursor = entry->end - cursor; + if ((entry->scroll = entry->start + scroll) > entry->end) + entry->scroll = entry->end; + + if ((entry->cursor = entry->end - cursor) > entry->end) + entry->cursor = entry->end; if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(entry), GNT_WIDGET_MAPPED)) entry_redraw(GNT_WIDGET(entry)); diff -r 994e8d214754 -r 585d6f844f79 finch/libgnt/gnttextview.c --- a/finch/libgnt/gnttextview.c Sun Nov 08 01:12:44 2009 +0000 +++ b/finch/libgnt/gnttextview.c Mon Nov 09 01:42:24 2009 +0000 @@ -67,6 +67,12 @@ static void reset_text_view(GntTextView *view); +static gboolean +text_view_contains(GntTextView *view, const char *str) +{ + return (str >= view->string->str && str < view->string->str + view->string->len); +} + static void gnt_text_view_draw(GntWidget *widget) { @@ -109,7 +115,7 @@ char back = *end; chtype fl = seg->flags; *end = '\0'; - if (select_start < view->string->str + seg->start && select_end > view->string->str + seg->end) { + if (select_start && select_start < view->string->str + seg->start && select_end > view->string->str + seg->end) { fl |= A_REVERSE; wattrset(widget->window, fl); wprintw(widget->window, "%s", (view->string->str + seg->start)); @@ -326,9 +332,10 @@ select_start = gnt_text_view_get_p(GNT_TEXT_VIEW(widget), x - widget->priv.x, y - widget->priv.y); g_timeout_add(500, too_slow, NULL); } else if (event == GNT_MOUSE_UP) { - if (select_start) { + GntTextView *view = GNT_TEXT_VIEW(widget); + if (text_view_contains(view, select_start)) { GString *clip; - select_end = gnt_text_view_get_p(GNT_TEXT_VIEW(widget), x - widget->priv.x, y - widget->priv.y); + select_end = gnt_text_view_get_p(view, x - widget->priv.x, y - widget->priv.y); if (select_end < select_start) { gchar *t = select_start; select_start = select_end; @@ -336,7 +343,7 @@ } if (select_start == select_end) { if (double_click) { - clip = select_word_text(GNT_TEXT_VIEW(widget), select_start); + clip = select_word_text(view, select_start); double_click = FALSE; } else { double_click = TRUE; diff -r 994e8d214754 -r 585d6f844f79 finch/libgnt/gnttree.c --- a/finch/libgnt/gnttree.c Sun Nov 08 01:12:44 2009 +0000 +++ b/finch/libgnt/gnttree.c Mon Nov 09 01:42:24 2009 +0000 @@ -815,7 +815,7 @@ gnt_widget_activate(widget); } else if (tree->priv->search) { gboolean changed = TRUE; - if (isalnum(*text)) { + if (g_unichar_isprint(*text)) { tree->priv->search = g_string_append_c(tree->priv->search, *text); } else if (g_utf8_collate(text, GNT_KEY_BACKSPACE) == 0) { if (tree->priv->search->len) diff -r 994e8d214754 -r 585d6f844f79 libpurple/Makefile.am --- a/libpurple/Makefile.am Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/Makefile.am Mon Nov 09 01:42:24 2009 +0000 @@ -32,7 +32,7 @@ pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = purple.pc -SUBDIRS = $(GCONF_DIR) plugins protocols tests . example +SUBDIRS = $(GCONF_DIR) plugins protocols . tests example purple_coresources = \ account.c \ diff -r 994e8d214754 -r 585d6f844f79 libpurple/account.c --- a/libpurple/account.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/account.c Mon Nov 09 01:42:24 2009 +0000 @@ -1050,6 +1050,16 @@ if(account->system_log) purple_log_free(account->system_log); + while (account->deny) { + g_free(account->deny->data); + account->deny = g_slist_delete_link(account->deny, account->deny); + } + + while (account->permit) { + g_free(account->permit->data); + account->permit = g_slist_delete_link(account->permit, account->permit); + } + priv = PURPLE_ACCOUNT_GET_PRIVATE(account); PURPLE_DBUS_UNREGISTER_POINTER(priv->current_error); if (priv->current_error) { diff -r 994e8d214754 -r 585d6f844f79 libpurple/certificate.c --- a/libpurple/certificate.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/certificate.c Mon Nov 09 01:42:24 2009 +0000 @@ -1402,13 +1402,15 @@ if (flags & PURPLE_CERTIFICATE_NAME_MISMATCH) { gchar *sn = purple_certificate_get_subject_name(peer_crt); - g_string_append_printf(errors, _("The certificate claims to be " - "from \"%s\" instead. This could mean that you are " - "not connecting to the service you believe you are."), - sn); - g_free(sn); + if (sn) { + g_string_append_printf(errors, _("The certificate claims to be " + "from \"%s\" instead. This could mean that you are " + "not connecting to the service you believe you are."), + sn); + g_free(sn); - flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH; + flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH; + } } while (i != PURPLE_CERTIFICATE_LAST) { diff -r 994e8d214754 -r 585d6f844f79 libpurple/cipher.c --- a/libpurple/cipher.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/cipher.c Mon Nov 09 01:42:24 2009 +0000 @@ -50,10 +50,6 @@ * 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 "internal.h" #include "cipher.h" #include "dbus-maybe.h" diff -r 994e8d214754 -r 585d6f844f79 libpurple/dnsquery.c --- a/libpurple/dnsquery.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/dnsquery.c Mon Nov 09 01:42:24 2009 +0000 @@ -172,6 +172,7 @@ return FALSE; } +#ifdef USE_IDN static gboolean dns_str_is_ascii(const char *name) { @@ -183,6 +184,7 @@ return TRUE; } +#endif #if defined(PURPLE_DNSQUERY_USE_FORK) @@ -293,12 +295,11 @@ rc = purple_network_convert_idn_to_ascii(dns_params.hostname, &hostname); if (rc != 0) { write_to_parent(child_out, &rc, sizeof(rc)); - close(child_out); if (show_debug) fprintf(stderr, "dns[%d] Error: IDN conversion returned " "%d\n", getpid(), rc); dns_params.hostname[0] = '\0'; - continue; + break; } } else /* intentional to execute the g_strdup */ #endif @@ -323,14 +324,13 @@ rc = getaddrinfo(hostname, servname, &hints, &res); write_to_parent(child_out, &rc, sizeof(rc)); if (rc != 0) { - close(child_out); if (show_debug) printf("dns[%d] Error: getaddrinfo returned %d\n", getpid(), rc); dns_params.hostname[0] = '\0'; g_free(hostname); hostname = NULL; - continue; + break; } tmp = res; while (res) { diff -r 994e8d214754 -r 585d6f844f79 libpurple/dnssrv.c --- a/libpurple/dnssrv.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/dnssrv.c Mon Nov 09 01:42:24 2009 +0000 @@ -248,6 +248,7 @@ return list; } +#ifdef USE_IDN static gboolean dns_str_is_ascii(const char *name) { @@ -259,8 +260,60 @@ return TRUE; } +#endif #ifndef _WIN32 +static void +write_to_parent(int in, int out, gconstpointer data, gsize size) +{ + const guchar *buf = data; + gssize w; + + do { + w = write(out, buf, size); + if (w > 0) { + buf += w; + size -= w; + } else if (w < 0 && errno == EINTR) { + /* Let's try some more; */ + w = 1; + } + } while (size > 0 && w > 0); + + if (size != 0) { + /* An error occurred */ + close(out); + close(in); + _exit(0); + } +} + +/* Read size bytes to data. Dies if an error occurs. */ +static void +read_from_parent(int in, int out, gpointer data, gsize size) +{ + guchar *buf = data; + gssize r; + + do { + r = read(in, data, size); + if (r > 0) { + buf += r; + size -= r; + } else if (r < 0 && errno == EINTR) { + /* Let's try some more; */ + r = 1; + } + } while (size > 0 && r > 0); + + if (size != 0) { + /* An error occurred */ + close(out); + close(in); + _exit(0); + } +} + G_GNUC_NORETURN static void resolve(int in, int out) @@ -279,16 +332,12 @@ purple_restore_default_signal_handlers(); #endif - if (read(in, &query, sizeof(query)) <= 0) { - close(out); - close(in); - _exit(0); - } + read_from_parent(in, out, &query, sizeof(query)); size = res_query( query.query, C_IN, query.type, (u_char*)&answer, sizeof( answer)); if (size == -1) { - write(out, &(query.type), sizeof(query.type)); - write(out, &size, sizeof(int)); + write_to_parent(in, out, &(query.type), sizeof(query.type)); + write_to_parent(in, out, &size, sizeof(size)); close(out); close(in); _exit(0); @@ -353,19 +402,17 @@ if (query.type == T_SRV) ret = purple_srv_sort(ret); - /* TODO: Check return value */ - write(out, &(query.type), sizeof(query.type)); - write(out, &size, sizeof(size)); + write_to_parent(in, out, &(query.type), sizeof(query.type)); + write_to_parent(in, out, &size, sizeof(size)); while (ret != NULL) { - /* TODO: Check return value */ if (query.type == T_SRV) - write(out, ret->data, sizeof(PurpleSrvResponse)); + write_to_parent(in, out, ret->data, sizeof(PurpleSrvResponse)); if (query.type == T_TXT) { PurpleTxtResponse *response = ret->data; gsize l = strlen(response->content) + 1 /* null byte */; - write(out, &l, sizeof(l)); - write(out, response->content, l); + write_to_parent(in, out, &l, sizeof(l)); + write_to_parent(in, out, response->content, l); } g_free(ret->data); diff -r 994e8d214754 -r 585d6f844f79 libpurple/ft.h --- a/libpurple/ft.h Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/ft.h Mon Nov 09 01:42:24 2009 +0000 @@ -674,7 +674,7 @@ void purple_xfer_ui_ready(PurpleXfer *xfer); /** - * Allows the prpl to signal it's readh to send/receive data (depending on + * Allows the prpl to signal it's ready to send/receive data (depending on * the direction of the file transfer. Used when the prpl provides read/write * ops and cannot/does not provide a raw fd to the core. * diff -r 994e8d214754 -r 585d6f844f79 libpurple/media.c --- a/libpurple/media.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/media.c Mon Nov 09 01:42:24 2009 +0000 @@ -103,6 +103,8 @@ gboolean initiator; gboolean accepted; gboolean candidates_prepared; + gboolean held; + gboolean paused; GList *active_local_candidates; GList *active_remote_candidates; @@ -281,7 +283,7 @@ { PURPLE_MEDIA_INFO_HOLD, "PURPLE_MEDIA_INFO_HOLD", "hold" }, { PURPLE_MEDIA_INFO_UNHOLD, - "PURPLE_MEDIA_INFO_HOLD", "unhold" }, + "PURPLE_MEDIA_INFO_UNHOLD", "unhold" }, { 0, NULL, NULL } }; type = g_enum_register_static("PurpleMediaInfoType", values); @@ -2330,11 +2332,46 @@ for (; streams; streams = g_list_delete_link(streams, streams)) { PurpleMediaStream *stream = streams->data; if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) { + stream->paused = active; + + if (!stream->held) + g_object_set(stream->stream, "direction", + purple_media_to_fs_stream_direction( + stream->session->type & ((active) ? + ~PURPLE_MEDIA_SEND_VIDEO : + PURPLE_MEDIA_VIDEO)), NULL); + } + } + } else if (local == TRUE && (type == PURPLE_MEDIA_INFO_HOLD || + type == PURPLE_MEDIA_INFO_UNHOLD)) { + GList *streams; + gboolean active = (type == PURPLE_MEDIA_INFO_HOLD); + + g_return_if_fail(PURPLE_IS_MEDIA(media)); + + streams = purple_media_get_streams(media, + session_id, participant); + for (; streams; streams = g_list_delete_link(streams, streams)) { + PurpleMediaStream *stream = streams->data; + stream->held = active; + if (stream->session->type & PURPLE_MEDIA_VIDEO) { + FsStreamDirection direction; + + direction = ((active) ? + ~PURPLE_MEDIA_VIDEO : + PURPLE_MEDIA_VIDEO); + if (!active && stream->paused) + direction &= ~PURPLE_MEDIA_SEND_VIDEO; + + g_object_set(stream->stream, "direction", + purple_media_to_fs_stream_direction( + stream->session->type & direction), NULL); + } else if (stream->session->type & PURPLE_MEDIA_AUDIO) { g_object_set(stream->stream, "direction", purple_media_to_fs_stream_direction( stream->session->type & ((active) ? - ~PURPLE_MEDIA_SEND_VIDEO : - PURPLE_MEDIA_VIDEO)), NULL); + ~PURPLE_MEDIA_AUDIO : + PURPLE_MEDIA_AUDIO)), NULL); } } } diff -r 994e8d214754 -r 585d6f844f79 libpurple/plugins/perl/Makefile.mingw --- a/libpurple/plugins/perl/Makefile.mingw Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/plugins/perl/Makefile.mingw Mon Nov 09 01:42:24 2009 +0000 @@ -7,10 +7,12 @@ PIDGIN_TREE_TOP := ../../.. include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak +DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES)) + TARGET = perl # Perl headers with /* /* */ type comments.. Turn off warnings. -CFLAGS += -Wno-comment +GCCWARNINGS += -Wno-comment ## ## INCLUDE PATHS diff -r 994e8d214754 -r 585d6f844f79 libpurple/plugins/perl/common/Makefile.mingw --- a/libpurple/plugins/perl/common/Makefile.mingw Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/plugins/perl/common/Makefile.mingw Mon Nov 09 01:42:24 2009 +0000 @@ -5,9 +5,12 @@ # PIDGIN_TREE_TOP := ../../../.. -GCCWARNINGS := -Wno-comment -Waggregate-return -Wcast-align -Wdeclaration-after-statement -Werror-implicit-function-declaration -Wextra -Wno-sign-compare -Wno-unused-parameter -Winit-self -Wmissing-declarations -Wmissing-prototypes -Wpointer-arith -Wundef -Wno-unused include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak +GCCWARNINGS += -Wno-comment -Wno-unused -Wno-nested-externs + +DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES)) + TARGET = Purple AUTOSPLIT = lib/auto/Purple/autosplit.ix EXTUTILS ?= C:/perl/lib/ExtUtils diff -r 994e8d214754 -r 585d6f844f79 libpurple/plugins/perl/perl-handlers.c --- a/libpurple/plugins/perl/perl-handlers.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/plugins/perl/perl-handlers.c Mon Nov 09 01:42:24 2009 +0000 @@ -649,6 +649,7 @@ static void destroy_cmd_handler(PurplePerlCmdHandler *handler) { + purple_cmd_unregister(handler->id); cmd_handlers = g_slist_remove(cmd_handlers, handler); if (handler->callback != NULL) @@ -705,7 +706,6 @@ return; } - purple_cmd_unregister(id); destroy_cmd_handler(handler); } diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/Makefile.am --- a/libpurple/protocols/Makefile.am Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/Makefile.am Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/bonjour/mdns_avahi.c --- a/libpurple/protocols/bonjour/mdns_avahi.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/bonjour/mdns_avahi.c Mon Nov 09 01:42:24 2009 +0000 @@ -150,6 +150,10 @@ } break; case AVAHI_RESOLVER_FOUND: + + purple_debug_info("bonjour", "_resolve_callback - name:%s account:%p bb:%p\n", + name, account, bb); + /* create a buddy record */ if (bb == NULL) bb = bonjour_buddy_new(name, account); @@ -173,8 +177,12 @@ /* Get the ip as a string */ + ip[0] = '\0'; avahi_address_snprint(ip, AVAHI_ADDRESS_STR_MAX, a); + purple_debug_info("bonjour", "_resolve_callback - name:%s ip:%s prev_ip:%s\n", + name, ip, rd->ip); + if (rd->ip == NULL || strcmp(rd->ip, ip) != 0) { /* We store duplicates in bb->ips, so we always remove the one */ if (rd->ip != NULL) { diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/JEPS --- a/libpurple/protocols/jabber/JEPS Sun Nov 08 01:12:44 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -0045: IN PROGRESS - Multi-User Chat -0047: IN PROGRESS - In-Band Bytestreams -0060: NEED - Pub-Sub -0071: AWAITING FINAL SPEC - XHTML-IM -0073: NEED - Basic IM Protocol Suite -0080: NEED (Do we?) - Geographic Location Information -0084: NEED - User Avatars in Jabber -0085: NEED - Chat State Notifications -0089: WATCH - Generic Alerts -0093: NEED - Roster Item Exchange -0100: NEED - Gateway Interaction (Transports) -0115: WATCH - Client Capabilities -0117: NEED - Intermediate IM Protocol Suite - diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/XEPS --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/XEPS Mon Nov 09 01:42:24 2009 +0000 @@ -0,0 +1,4 @@ +0060: NEED + Pub-Sub +0080: NEED (Do we?) + Geographic Location Information diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/auth.c --- a/libpurple/protocols/jabber/auth.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/auth.c Mon Nov 09 01:42:24 2009 +0000 @@ -58,7 +58,7 @@ PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("Server requires TLS/SSL, but no TLS/SSL support was found.")); return TRUE; - } else if(purple_account_get_bool(js->gc->account, "require_tls", FALSE)) { + } else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("You require encryption, but no TLS/SSL support was found.")); @@ -381,13 +381,13 @@ * due to mechanism specific issues, so we want to try one of the other * supported mechanisms. This code handles that case */ - if (js->current_mech && strlen(js->current_mech) > 0) { + if (js->current_mech && *js->current_mech) { char *pos; if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) { g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech)); } /* Remove space which separated this mech from the next */ - if (strlen(js->sasl_mechs->str) > 0 && ((js->sasl_mechs->str)[0] == ' ')) { + if ((js->sasl_mechs->str)[0] == ' ') { g_string_erase(js->sasl_mechs, 0, 1); } again = TRUE; @@ -511,7 +511,7 @@ * support it and including it gives a false fall-back to other mechs offerred, * leading to incorrect error handling. */ - if (mech_name && !strcmp(mech_name, "X-GOOGLE-TOKEN")) { + if (purple_strequal(mech_name, "X-GOOGLE-TOKEN")) { g_free(mech_name); continue; } @@ -519,9 +519,9 @@ g_string_append(js->sasl_mechs, mech_name); g_string_append_c(js->sasl_mechs, ' '); #else - if(mech_name && !strcmp(mech_name, "DIGEST-MD5")) + if (purple_strequal(mech_name, "DIGEST-MD5")) digest_md5 = TRUE; - else if(mech_name && !strcmp(mech_name, "PLAIN")) + else if (purple_strequal(mech_name, "PLAIN")) plain = TRUE; #endif g_free(mech_name); @@ -586,7 +586,7 @@ /* FIXME: Why is this not in jabber_parse_error? */ if((error = xmlnode_get_child(packet, "error")) && (err_code = xmlnode_get_attrib(error, "code")) && - !strcmp(err_code, "401")) { + g_str_equal(err_code, "401")) { reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; /* Clear the pasword if it isn't being saved */ if (!purple_account_get_remember_password(js->gc->account)) @@ -698,7 +698,7 @@ * is requiring SSL/TLS, we need to enforce it. */ if (!jabber_stream_is_ssl(js) && - purple_account_get_bool(purple_connection_get_account(js->gc), "require_tls", FALSE)) { + purple_account_get_bool(purple_connection_get_account(js->gc), "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, _("You require encryption, but it is not available on this server.")); @@ -877,7 +877,7 @@ } dec_in = (char *)purple_base64_decode(enc_in, NULL); - purple_debug(PURPLE_DEBUG_MISC, "jabber", "decoded challenge (%" + purple_debug_misc("jabber", "decoded challenge (%" G_GSIZE_FORMAT "): %s\n", strlen(dec_in), dec_in); parts = parse_challenge(dec_in); @@ -887,8 +887,7 @@ char *rspauth = g_hash_table_lookup(parts, "rspauth"); - if(rspauth && js->expected_rspauth && - !strcmp(rspauth, js->expected_rspauth)) { + if (rspauth && purple_strequal(rspauth, js->expected_rspauth)) { jabber_send_raw(js, "", -1); @@ -1014,7 +1013,7 @@ * realm are always encoded in UTF-8 (they seem to be the values * we pass in), so we need to ensure charset=utf-8 is set. */ - if (!js->current_mech || !g_str_equal(js->current_mech, "DIGEST-MD5") || + if (!purple_strequal(js->current_mech, "DIGEST-MD5") || strstr(c_out, ",charset=")) /* If we're not using DIGEST-MD5 or Cyrus SASL is fixed */ enc_out = purple_base64_encode((unsigned char*)c_out, clen); @@ -1041,7 +1040,7 @@ const void *x; #endif - if(!ns || strcmp(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) { + if (!purple_strequal(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Invalid response from server")); @@ -1072,6 +1071,7 @@ purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Invalid response from server")); + g_return_if_reached(); } } /* If we've negotiated a security layer, we need to enable it */ @@ -1099,17 +1099,17 @@ #ifdef HAVE_CYRUS_SASL if(js->auth_fail_count++ < 5) { - if (js->current_mech && strlen(js->current_mech) > 0) { + if (js->current_mech && *js->current_mech) { char *pos; if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) { g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech)); } /* Remove space which separated this mech from the next */ - if (strlen(js->sasl_mechs->str) > 0 && ((js->sasl_mechs->str)[0] == ' ')) { + if ((js->sasl_mechs->str)[0] == ' ') { g_string_erase(js->sasl_mechs, 0, 1); } } - if (strlen(js->sasl_mechs->str)) { + if (*js->sasl_mechs->str) { /* If we have remaining mechs to try, do so */ sasl_dispose(&js->sasl); diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/buddy.c --- a/libpurple/protocols/jabber/buddy.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/buddy.c Mon Nov 09 01:42:24 2009 +0000 @@ -581,8 +581,7 @@ if (text != NULL && *text != '\0') { xmlnode *xp; - purple_debug(PURPLE_DEBUG_INFO, "jabber", - "Setting %s to '%s'\n", vc_tp->tag, text); + purple_debug_info("jabber", "Setting %s to '%s'\n", vc_tp->tag, text); if ((xp = insert_tag_to_parent_tag(vc_node, NULL, vc_tp->tag)) != NULL) { diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/caps.c --- a/libpurple/protocols/jabber/caps.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/caps.c Mon Nov 09 01:42:24 2009 +0000 @@ -975,7 +975,7 @@ g_free(js->caps_hash); js->caps_hash = jabber_caps_calculate_hash(&info, "sha1"); g_list_free(info.identities); - g_list_free(features); + g_list_free(info.features); } const gchar* jabber_caps_get_own_hash(JabberStream *js) diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/chat.c --- a/libpurple/protocols/jabber/chat.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/chat.c Mon Nov 09 01:42:24 2009 +0000 @@ -106,7 +106,7 @@ { char *room_jid = g_strdup_printf("%s@%s", room, server); - chat = g_hash_table_lookup(js->chats, jabber_normalize(NULL, room_jid)); + chat = g_hash_table_lookup(js->chats, room_jid); g_free(room_jid); } @@ -177,10 +177,21 @@ xmlnode_insert_data(body, msg, -1); } else { xmlnode_set_attrib(message, "to", name); + /* + * Putting the reason into the body was an 'undocumented protocol, + * ...not part of "groupchat 1.0"'. + * http://xmpp.org/extensions/attic/jep-0045-1.16.html#invite + * + * Left here for compatibility. + */ body = xmlnode_new_child(message, "body"); xmlnode_insert_data(body, msg, -1); + x = xmlnode_new_child(message, "x"); xmlnode_set_attrib(x, "jid", room_jid); + + /* The better place for it! XEP-0249 style. */ + xmlnode_set_attrib(x, "reason", msg); xmlnode_set_namespace(x, "jabber:x:conference"); } @@ -216,7 +227,8 @@ JabberChat *chat; char *jid; - g_return_val_if_fail(jabber_chat_find(js, room, server) == NULL, NULL); + if (jabber_chat_find(js, room, server) != NULL) + return NULL; chat = g_new0(JabberChat, 1); chat->js = js; @@ -264,7 +276,8 @@ char *jid; chat = jabber_chat_new(js, room, server, handle, password, data); - g_return_val_if_fail(chat != NULL, NULL); + if (chat == NULL) + return NULL; gc = js->gc; account = purple_connection_get_account(gc); @@ -371,7 +384,7 @@ JabberStream *js = chat->js; char *room_jid = g_strdup_printf("%s@%s", chat->room, chat->server); - g_hash_table_remove(js->chats, jabber_normalize(NULL, room_jid)); + g_hash_table_remove(js->chats, room_jid); g_free(room_jid); } @@ -679,11 +692,11 @@ } -void jabber_chat_change_nick(JabberChat *chat, const char *nick) +gboolean jabber_chat_change_nick(JabberChat *chat, const char *nick) { xmlnode *presence; char *full_jid; - PurplePresence *gpresence; + PurpleAccount *account; PurpleStatus *status; JabberBuddyState state; char *msg; @@ -693,11 +706,11 @@ purple_conv_chat_write(PURPLE_CONV_CHAT(chat->conv), "", _("Nick changing not supported in non-MUC chatrooms"), PURPLE_MESSAGE_SYSTEM, time(NULL)); - return; + return FALSE; } - gpresence = purple_account_get_presence(chat->js->gc->account); - status = purple_presence_get_active_status(gpresence); + account = purple_connection_get_account(chat->js->gc); + status = purple_account_get_active_status(account); purple_status_to_jabber(status, &state, &msg, &priority); @@ -709,6 +722,8 @@ jabber_send(chat->js, presence); xmlnode_free(presence); + + return TRUE; } void jabber_chat_part(JabberChat *chat, const char *msg) diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/chat.h --- a/libpurple/protocols/jabber/chat.h Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/chat.h Mon Nov 09 01:42:24 2009 +0000 @@ -62,6 +62,8 @@ * in-prpl function for joining a chat room. Doesn't require sticking goop * into a hash table. * + * @param room The room to join. This MUST be normalized already. + * @param server The server the room is on. This MUST be normalized already. * @param password The password (if required) to join the room. May be NULL. * @param data The chat hash table. May be NULL (it will be generated * for current core<>prpl API interface.) @@ -87,7 +89,7 @@ void jabber_chat_register(JabberChat *chat); void jabber_chat_change_topic(JabberChat *chat, const char *topic); void jabber_chat_set_topic(PurpleConnection *gc, int id, const char *topic); -void jabber_chat_change_nick(JabberChat *chat, const char *nick); +gboolean jabber_chat_change_nick(JabberChat *chat, const char *nick); void jabber_chat_part(JabberChat *chat, const char *msg); void jabber_chat_track_handle(JabberChat *chat, const char *handle, const char *jid, const char *affiliation, const char *role); diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/disco.c --- a/libpurple/protocols/jabber/disco.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/disco.c Mon Nov 09 01:42:24 2009 +0000 @@ -421,6 +421,76 @@ } +/* should probably share this code with google.c, or maybe from 2.7.0 + introduce an abstracted hostname -> IP function in dns.c */ +static void +jabber_disco_stun_lookup_cb(GSList *hosts, gpointer data, + const char *error_message) +{ + JabberStream *js = (JabberStream *) data; + + if (error_message) { + purple_debug_error("jabber", "STUN lookup failed: %s\n", + error_message); + g_slist_free(hosts); + js->stun_query = NULL; + return; + } + + if (hosts && g_slist_next(hosts)) { + struct sockaddr *addr = g_slist_next(hosts)->data; + char dst[INET6_ADDRSTRLEN]; + int port; + + if (addr->sa_family == AF_INET6) { + inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr, + dst, sizeof(dst)); + port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port); + } else { + inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr, + dst, sizeof(dst)); + port = ntohs(((struct sockaddr_in *) addr)->sin_port); + } + + if (js->stun_ip) + g_free(js->stun_ip); + js->stun_ip = g_strdup(dst); + js->stun_port = port; + + purple_debug_info("jabber", "set STUN IP/port address: " + "%s:%d\n", dst, port); + + /* unmark ongoing query */ + js->stun_query = NULL; + } + + while (hosts != NULL) { + hosts = g_slist_delete_link(hosts, hosts); + /* Free the address */ + g_free(hosts->data); + hosts = g_slist_delete_link(hosts, hosts); + } +} + + +static void +jabber_disco_stun_srv_resolve_cb(PurpleSrvResponse *resp, int results, gpointer data) +{ + JabberStream *js = (JabberStream *) data; + + purple_debug_info("jabber", "got %d SRV responses for STUN.\n", results); + js->srv_query_data = NULL; + + if (results > 0) { + purple_debug_info("jabber", "looking up IP for %s:%d\n", + resp[0].hostname, resp[0].port); + js->stun_query = + purple_dnsquery_a(resp[0].hostname, resp[0].port, + jabber_disco_stun_lookup_cb, js); + } +} + + static void jabber_disco_server_info_result_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, @@ -471,7 +541,10 @@ /* autodiscover stun and relays */ jabber_google_send_jingle_info(js); } else { - /* TODO: add external service discovery here... */ + js->srv_query_data = + purple_srv_resolve("stun", "udp", js->user->domain, + jabber_disco_stun_srv_resolve_cb, js); + /* TODO: add TURN support later... */ } } diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/google.c --- a/libpurple/protocols/jabber/google.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/google.c Mon Nov 09 01:42:24 2009 +0000 @@ -910,7 +910,7 @@ xmlnode_set_attrib(iq->node, "id", id); jabber_iq_send(iq); - purple_debug(PURPLE_DEBUG_MISC, "jabber", + purple_debug_misc("jabber", "Got new mail notification. Sending request for more info\n"); iq = jabber_iq_new_query(js, JABBER_IQ_GET, "google:mail:notify"); @@ -994,8 +994,9 @@ const char *grt = xmlnode_get_attrib_with_namespace(item, "t", "google:roster"); const char *subscription = xmlnode_get_attrib(item, "subscription"); + const char *ask = xmlnode_get_attrib(item, "ask"); - if (!subscription || !strcmp(subscription, "none")) { + if ((!subscription || !strcmp(subscription, "none")) && !ask) { /* The Google Talk servers will automatically add people from your Gmail address book * with subscription=none. If we see someone with subscription=none, ignore them. */ @@ -1093,12 +1094,13 @@ jbr = l->data; if (jbr && jbr->name) { - purple_debug(PURPLE_DEBUG_MISC, "jabber", "Removing resource %s\n", jbr->name); + purple_debug_misc("jabber", "Removing resource %s\n", jbr->name); jabber_buddy_remove_resource(jb, jbr->name); } l = l->next; } } + purple_prpl_got_user_status(purple_connection_get_account(gc), who, "offline", NULL); } diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/iq.c --- a/libpurple/protocols/jabber/iq.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/iq.c Mon Nov 09 01:42:24 2009 +0000 @@ -342,7 +342,7 @@ return; } - signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin, + signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), "jabber-receiving-iq", js->gc, iq_type, id, from, packet)); if (signal_return) return; @@ -367,7 +367,7 @@ g_free(key); if (signal_ref > 0) { - signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin, "jabber-watched-iq", + signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), "jabber-watched-iq", js->gc, iq_type, id, from, child)); if (signal_return) return; diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.c Mon Nov 09 01:42:24 2009 +0000 @@ -68,10 +68,9 @@ #include "jingle/jingle.h" #include "jingle/rtp.h" -PurplePlugin *jabber_plugin = NULL; GList *jabber_features = NULL; GList *jabber_identities = NULL; -GSList *jabber_cmds = NULL; +static GSList *jabber_cmds = NULL; static void jabber_unregister_account_cb(JabberStream *js); static void try_srv_connect(JabberStream *js); @@ -200,7 +199,7 @@ jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION); return; } - } else if(purple_account_get_bool(js->gc->account, "require_tls", FALSE) && !jabber_stream_is_ssl(js)) { + } else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS) && !jabber_stream_is_ssl(js)) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, _("You require encryption, but it is not available on this server.")); @@ -255,7 +254,7 @@ { const char *xmlns; - purple_signal_emit(jabber_plugin, "jabber-receiving-xmlnode", js->gc, packet); + purple_signal_emit(purple_connection_get_prpl(js->gc), "jabber-receiving-xmlnode", js->gc, packet); /* if the signal leaves us with a null packet, we're done */ if(NULL == *packet) @@ -294,8 +293,7 @@ else purple_debug_warning("jabber", "Ignoring spurious \n"); } else { - purple_debug(PURPLE_DEBUG_WARNING, "jabber", "Unknown packet: %s\n", - (*packet)->name); + purple_debug_warning("jabber", "Unknown packet: %s\n", (*packet)->name); } } @@ -377,9 +375,9 @@ void jabber_send_raw(JabberStream *js, const char *data, int len) { - /* because printing a tab to debug every minute gets old */ if(strcmp(data, "\t")) { + const char *username; char *text = NULL, *last_part = NULL, *tag_start = NULL; /* Because debug logs with plaintext passwords make me sad */ @@ -404,8 +402,13 @@ *data_start = '\0'; } - purple_debug(PURPLE_DEBUG_MISC, "jabber", "Sending%s: %s%s%s\n", - jabber_stream_is_ssl(js) ? " (ssl)" : "", text ? text : data, + username = purple_connection_get_display_name(js->gc); + if (!username) + username = purple_account_get_username(purple_connection_get_account(js->gc)); + + purple_debug_misc("jabber", "Sending%s (%s): %s%s%s\n", + jabber_stream_is_ssl(js) ? " (ssl)" : "", username, + text ? text : data, last_part ? "password removed" : "", last_part ? last_part : ""); @@ -415,7 +418,7 @@ /* If we've got a security layer, we need to encode the data, * splitting it on the maximum buffer length negotiated */ - purple_signal_emit(jabber_plugin, "jabber-sending-text", js->gc, &data); + purple_signal_emit(purple_connection_get_prpl(js->gc), "jabber-sending-text", js->gc, &data); if (data == NULL) return; @@ -485,7 +488,7 @@ void jabber_send(JabberStream *js, xmlnode *packet) { - purple_signal_emit(jabber_plugin, "jabber-sending-xmlnode", js->gc, &packet); + purple_signal_emit(purple_connection_get_prpl(js->gc), "jabber-sending-xmlnode", js->gc, &packet); } static gboolean jabber_keepalive_timeout(PurpleConnection *gc) @@ -526,7 +529,7 @@ while((len = purple_ssl_read(gsc, buf, sizeof(buf) - 1)) > 0) { gc->last_received = time(NULL); buf[len] = '\0'; - purple_debug(PURPLE_DEBUG_INFO, "jabber", "Recv (ssl)(%d): %s\n", len, buf); + purple_debug_info("jabber", "Recv (ssl)(%d): %s\n", len, buf); jabber_parser_process(js, buf, len); if(js->reinit) jabber_stream_init(js); @@ -566,7 +569,7 @@ unsigned int olen; sasl_decode(js->sasl, buf, len, &out, &olen); if (olen>0) { - purple_debug(PURPLE_DEBUG_INFO, "jabber", "RecvSASL (%u): %s\n", olen, out); + purple_debug_info("jabber", "RecvSASL (%u): %s\n", olen, out); jabber_parser_process(js,out,olen); if(js->reinit) jabber_stream_init(js); @@ -575,7 +578,7 @@ } #endif buf[len] = '\0'; - purple_debug(PURPLE_DEBUG_INFO, "jabber", "Recv (%d): %s\n", len, buf); + purple_debug_info("jabber", "Recv (%d): %s\n", len, buf); jabber_parser_process(js, buf, len); if(js->reinit) jabber_stream_init(js); @@ -1825,7 +1828,7 @@ JabberFeature *feature = jabber_features->data; g_free(feature->namespace); g_free(feature); - jabber_features = g_list_remove_link(jabber_features, jabber_features); + jabber_features = g_list_delete_link(jabber_features, jabber_features); } } @@ -1862,7 +1865,7 @@ g_free(id->lang); g_free(id->name); g_free(id); - jabber_identities = g_list_remove_link(jabber_identities, jabber_identities); + jabber_identities = g_list_delete_link(jabber_identities, jabber_identities); } } @@ -2606,8 +2609,15 @@ if(!chat || !args || !args[0]) return PURPLE_CMD_RET_FAILED; - jabber_chat_change_nick(chat, args[0]); - return PURPLE_CMD_RET_OK; + if (!jabber_resourceprep_validate(args[0])) { + *error = g_strdup(_("Invalid nickname")); + return PURPLE_CMD_RET_FAILED; + } + + if (jabber_chat_change_nick(chat, args[0])) + return PURPLE_CMD_RET_OK; + else + return PURPLE_CMD_RET_FAILED; } static PurpleCmdRet jabber_cmd_chat_part(PurpleConversation *conv, @@ -3239,7 +3249,7 @@ id = purple_cmd_register("part", "s", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber", - jabber_cmd_chat_part, _("part [room]: Leave the room."), + jabber_cmd_chat_part, _("part [message]: Leave the room."), NULL); jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id)); @@ -3394,8 +3404,6 @@ unspecified */ const gchar *ui_name = NULL; - jabber_plugin = plugin; - ui_type = ui_info ? g_hash_table_lookup(ui_info, "client_type") : NULL; if (ui_type) { if (strcmp(ui_type, "pc") == 0 || @@ -3485,9 +3493,9 @@ } void -jabber_uninit_plugin(void) +jabber_uninit_plugin(PurplePlugin *plugin) { - purple_plugin_ipc_unregister_all(jabber_plugin); + purple_plugin_ipc_unregister_all(plugin); jabber_features_destroy(); jabber_identities_destroy(); diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/jabber.h --- a/libpurple/protocols/jabber/jabber.h Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.h Mon Nov 09 01:42:24 2009 +0000 @@ -76,11 +76,11 @@ #define CAPS0115_NODE "http://pidgin.im/" +#define JABBER_DEFAULT_REQUIRE_TLS TRUE + /* Index into attention_types list */ #define JABBER_BUZZ 0 -extern PurplePlugin *jabber_plugin; - typedef enum { JABBER_STREAM_OFFLINE, JABBER_STREAM_CONNECTING, @@ -193,25 +193,16 @@ char *serverFQDN; - /* OK, this stays at the end of the struct, so plugins can depend - * on the rest of the stuff being in the right place - */ #ifdef HAVE_CYRUS_SASL sasl_conn_t *sasl; sasl_callback_t *sasl_cb; -#else /* keep the struct the same size */ - void *sasl; - void *sasl_cb; -#endif - /* did someone say something about the end of the struct? */ -#ifdef HAVE_CYRUS_SASL const char *current_mech; int auth_fail_count; -#endif int sasl_state; int sasl_maxbuf; GString *sasl_mechs; +#endif gboolean unregistration; PurpleAccountUnregistrationCb unregistration_cb; @@ -382,6 +373,6 @@ void jabber_unregister_commands(void); void jabber_init_plugin(PurplePlugin *plugin); -void jabber_uninit_plugin(void); +void jabber_uninit_plugin(PurplePlugin *plugin); #endif /* PURPLE_JABBER_H_ */ diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/jingle/jingle.c --- a/libpurple/protocols/jabber/jingle/jingle.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/jingle/jingle.c Mon Nov 09 01:42:24 2009 +0000 @@ -442,15 +442,15 @@ if (num_params > 0) { params = g_new0(GParameter, num_params); - purple_debug_info("jabber", - "setting param stun-ip for stream using Google auto-config: %s\n", - js->stun_ip); + purple_debug_info("jabber", + "setting param stun-ip for stream using auto-discovered IP: %s\n", + js->stun_ip); params[0].name = "stun-ip"; g_value_init(¶ms[0].value, G_TYPE_STRING); g_value_set_string(¶ms[0].value, js->stun_ip); purple_debug_info("jabber", - "setting param stun-port for stream using Google auto-config: %d\n", - js->stun_port); + "setting param stun-port for stream using auto-discovered port: %d\n", + js->stun_port); params[1].name = "stun-port"; g_value_init(¶ms[1].value, G_TYPE_UINT); g_value_set_uint(¶ms[1].value, js->stun_port); diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Mon Nov 09 01:42:24 2009 +0000 @@ -227,7 +227,7 @@ jabber_unregister_commands(); /* Stay on target...stay on target... Almost there... */ - jabber_uninit_plugin(); + jabber_uninit_plugin(plugin); return TRUE; } @@ -355,7 +355,7 @@ purple_account_user_split_set_reverse(split, FALSE); prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); - option = purple_account_option_bool_new(_("Require SSL/TLS"), "require_tls", TRUE); + option = purple_account_option_bool_new(_("Require SSL/TLS"), "require_tls", JABBER_DEFAULT_REQUIRE_TLS); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/message.c --- a/libpurple/protocols/jabber/message.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/message.c Mon Nov 09 01:42:24 2009 +0000 @@ -545,7 +545,7 @@ to = xmlnode_get_attrib(packet, "to"); type = xmlnode_get_attrib(packet, "type"); - signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin, + signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), "jabber-receiving-message", js->gc, type, id, from, to, packet)); if (signal_return) return; @@ -758,9 +758,22 @@ jm->type != JABBER_MESSAGE_ERROR) { const char *jid = xmlnode_get_attrib(child, "jid"); if(jid) { + const char *reason = xmlnode_get_attrib(child, "reason"); + const char *password = xmlnode_get_attrib(child, "password"); + jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE; g_free(jm->to); jm->to = g_strdup(jid); + + if (reason) { + g_free(jm->body); + jm->body = g_strdup(reason); + } + + if (password) { + g_free(jm->password); + jm->password = g_strdup(password); + } } } else if(!strcmp(xmlns, "http://jabber.org/protocol/muc#user") && jm->type != JABBER_MESSAGE_ERROR) { @@ -775,8 +788,10 @@ g_free(jm->body); jm->body = xmlnode_get_data(reason); } - if((password = xmlnode_get_child(child, "password"))) + if((password = xmlnode_get_child(child, "password"))) { + g_free(jm->password); jm->password = xmlnode_get_data(password); + } jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE; } @@ -797,7 +812,7 @@ switch(jm->type) { case JABBER_MESSAGE_OTHER: - purple_debug(PURPLE_DEBUG_INFO, "jabber", + purple_debug_info("jabber", "Received message of unknown type: %s\n", type); /* Fall-through is intentional */ case JABBER_MESSAGE_NORMAL: @@ -1088,7 +1103,7 @@ if ((child = xmlnode_from_str(jm->xhtml, -1))) { xmlnode_insert_child(message, child); } else { - purple_debug(PURPLE_DEBUG_ERROR, "jabber", + purple_debug_error("jabber", "XHTML translation/validation failed, returning: %s\n", jm->xhtml); } diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/oob.c --- a/libpurple/protocols/jabber/oob.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/oob.c Mon Nov 09 01:42:24 2009 +0000 @@ -95,7 +95,7 @@ if(len < 0 && errno == EAGAIN) return; else if(len < 0) { - purple_debug(PURPLE_DEBUG_ERROR, "jabber", "Write error on oob xfer!\n"); + purple_debug_error("jabber", "Write error on oob xfer!\n"); purple_input_remove(jox->writeh); purple_xfer_cancel_local(xfer); } @@ -150,7 +150,7 @@ } return 0; } else if (errno != EAGAIN) { - purple_debug(PURPLE_DEBUG_ERROR, "jabber", "Read error on oob xfer!\n"); + purple_debug_error("jabber", "Read error on oob xfer!\n"); purple_xfer_cancel_local(xfer); } diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/presence.c --- a/libpurple/protocols/jabber/presence.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/presence.c Mon Nov 09 01:42:24 2009 +0000 @@ -476,7 +476,7 @@ /* * Versions of libpurple before 2.6.0 didn't advertise this capability, so * we can't yet use Entity Capabilities to determine whether or not the - * other client supports Entity Capabilities. + * other client supports Chat States. */ if (jabber_resource_has_capability(jbr, "http://jabber.org/protocol/chatstates")) jbr->chat_states = JABBER_CHAT_STATES_SUPPORTED; @@ -518,7 +518,7 @@ jb = jabber_buddy_find(js, from, TRUE); g_return_if_fail(jb != NULL); - signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin, + signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), "jabber-receiving-presence", js->gc, type, from, packet)); if (signal_return) return; diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/roster.c --- a/libpurple/protocols/jabber/roster.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/roster.c Mon Nov 09 01:42:24 2009 +0000 @@ -72,7 +72,7 @@ const char *alias, GSList *groups) { GSList *buddies, *l; - GSList *pool = NULL; + PurpleAccount *account = purple_connection_get_account(js->gc); buddies = purple_find_buddies(js->gc->account, jid); @@ -117,25 +117,14 @@ groups = g_slist_delete_link(groups, l); } else { /* This buddy isn't in the group on the server anymore */ - pool = g_slist_prepend(pool, b); + purple_debug_info("jabber", "jabber_roster_parse(): Removing %s " + "from group '%s' on the local list\n", + purple_buddy_get_name(b), + purple_group_get_name(g)); + purple_blist_remove_buddy(b); } } - if (pool) { - GString *tmp = g_string_new(NULL); - GSList *list = pool; - for ( ; list; list = list->next) { - tmp = g_string_append(tmp, - purple_group_get_name(purple_buddy_get_group(list->data))); - if (list->next) - tmp = g_string_append(tmp, ", "); - } - - purple_debug_info("jabber", "jabber_roster_parse(): Removing %s from " - "groups: %s\n", jid, tmp->str); - g_string_free(tmp, TRUE); - } - if (groups) { char *tmp = roster_groups_join(groups); purple_debug_info("jabber", "jabber_roster_parse(): Adding %s to " @@ -145,17 +134,7 @@ while(groups) { PurpleGroup *g = purple_find_group(groups->data); - PurpleBuddy *b = NULL; - - /* If there are buddies we would otherwise delete, move them to - * the new group (instead of deleting them below) - */ - if (pool) { - b = pool->data; - pool = g_slist_delete_link(pool, pool); - } else { - b = purple_buddy_new(js->gc->account, jid, alias); - } + PurpleBuddy *b = purple_buddy_new(account, jid, alias); if(!g) { g = purple_group_new(groups->data); @@ -169,14 +148,6 @@ groups = g_slist_delete_link(groups, groups); } - /* Remove this person from all the groups they're no longer in on the - * server */ - while (pool) { - PurpleBuddy *b = pool->data; - purple_blist_remove_buddy(b); - pool = g_slist_delete_link(pool, pool); - } - g_slist_free(buddies); } diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/si.c --- a/libpurple/protocols/jabber/si.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/si.c Mon Nov 09 01:42:24 2009 +0000 @@ -1349,7 +1349,7 @@ jabber_ibb_session_close(jsx->ibb_session); } jabber_si_xfer_free(xfer); - purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_send\n"); + purple_debug_info("jabber", "in jabber_si_xfer_cancel_send\n"); } @@ -1381,7 +1381,7 @@ } jabber_si_xfer_free(xfer); - purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_request_denied\n"); + purple_debug_info("jabber", "in jabber_si_xfer_request_denied\n"); } @@ -1393,7 +1393,7 @@ jabber_ibb_session_close(jsx->ibb_session); } jabber_si_xfer_free(xfer); - purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_recv\n"); + purple_debug_info("jabber", "in jabber_si_xfer_cancel_recv\n"); } diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/jabber/useravatar.c --- a/libpurple/protocols/jabber/useravatar.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/jabber/useravatar.c Mon Nov 09 01:42:24 2009 +0000 @@ -262,7 +262,7 @@ gpointer icon_data; if(!url_text) { - purple_debug(PURPLE_DEBUG_ERROR, "jabber", + purple_debug_error("jabber", "do_buddy_avatar_update_fromurl got error \"%s\"", error_message); goto out; diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/msn/contact.c --- a/libpurple/protocols/msn/contact.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/msn/contact.c Mon Nov 09 01:42:24 2009 +0000 @@ -362,7 +362,7 @@ char *display_text; passport = xmlnode_get_data(xmlnode_get_child(member, node)); - if (!purple_email_is_valid(passport)) { + if (!msn_email_is_valid(passport)) { g_free(passport); return; } @@ -765,7 +765,7 @@ if (passport == NULL) continue; - if (!purple_email_is_valid(passport)) + if (!msn_email_is_valid(passport)) continue; if ((displayName = xmlnode_get_child(contactInfo, "displayName"))) @@ -1232,8 +1232,13 @@ if (user->invite_message) { char *tmp; body = g_markup_escape_text(user->invite_message, -1); - tmp = g_markup_escape_text(purple_connection_get_display_name(session->account->gc), -1); + + /* Ignore the cast, we treat it as const anyway. */ + tmp = (char *)purple_connection_get_display_name(session->account->gc); + tmp = tmp ? g_markup_escape_text(tmp, -1) : g_strdup(""); + invite = g_strdup_printf(MSN_CONTACT_INVITE_MESSAGE_XML, body, tmp); + g_free(body); g_free(tmp); diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/msn/msn.c --- a/libpurple/protocols/msn/msn.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/msn/msn.c Mon Nov 09 01:42:24 2009 +0000 @@ -118,6 +118,29 @@ return buf; } +gboolean +msn_email_is_valid(const char *passport) +{ + if (purple_email_is_valid(passport)) { + /* Special characters aren't allowed in domains, so only go to '@' */ + while (*passport != '@') { + if (*passport == '/') + return FALSE; + else if (*passport == '?') + return FALSE; + else if (*passport == '=') + return FALSE; + /* MSN also doesn't like colons, but that's checked already */ + + passport++; + } + + return TRUE; + } + + return FALSE; +} + static gboolean msn_send_attention(PurpleConnection *gc, const char *username, guint type) { @@ -611,9 +634,14 @@ MsnSession *session = gc->proto_data; if (session) { MsnUser *user = msn_userlist_find_user(session->userlist, who); - if (user) + if (user) { /* Include these too: MSN_CLIENT_CAP_MSNMOBILE|MSN_CLIENT_CAP_MSNDIRECT ? */ - ret = (user->clientid & MSN_CLIENT_CAP_WEBMSGR) == 0; + if ((user->clientid & MSN_CLIENT_CAP_WEBMSGR) || + user->networkid == MSN_NETWORK_YAHOO) + ret = FALSE; + else + ret = TRUE; + } } else ret = FALSE; } @@ -1511,7 +1539,7 @@ bname = purple_buddy_get_name(buddy); - if (!purple_email_is_valid(bname)) { + if (!msn_email_is_valid(bname)) { gchar *buf; buf = g_strdup_printf(_("Unable to add the buddy %s because the username is invalid. Usernames must be valid email addresses."), bname); if (!purple_conv_present_error(bname, purple_connection_get_account(gc), buf)) diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/msn/msn.h --- a/libpurple/protocols/msn/msn.h Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/msn/msn.h Mon Nov 09 01:42:24 2009 +0000 @@ -133,6 +133,7 @@ ((MSN_CLIENT_ID_VERSION << 24) | \ (MSN_CLIENT_ID_CAPABILITIES)) +gboolean msn_email_is_valid(const char *passport); void msn_act_id(PurpleConnection *gc, const char *entry); void msn_send_privacy(PurpleConnection *gc); void msn_send_im_message(MsnSession *session, MsnMessage *msg); diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/msn/nexus.c --- a/libpurple/protocols/msn/nexus.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/msn/nexus.c Mon Nov 09 01:42:24 2009 +0000 @@ -399,7 +399,14 @@ username = purple_account_get_username(session->account); password = purple_connection_get_password(session->account->gc); - password_xml = g_markup_escape_text(password, MIN(strlen(password), 16)); + if (g_utf8_strlen(password, -1) > 16) { + /* max byte size for 16 utf8 characters is 64 + 1 for the null */ + gchar truncated[65]; + g_utf8_strncpy(truncated, password, 16); + password_xml = g_markup_escape_text(truncated, -1); + } else { + password_xml = g_markup_escape_text(password, -1); + } purple_debug_info("msn", "Logging on %s, with policy '%s', nonce '%s'\n", username, nexus->policy, nexus->nonce); diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/msn/notification.c --- a/libpurple/protocols/msn/notification.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/msn/notification.c Mon Nov 09 01:42:24 2009 +0000 @@ -670,7 +670,7 @@ "User %s is on both Allow and Block list; " "removing from Allow list.\n", user->passport); - msn_userlist_rem_buddy_from_list(session->userlist, user->passport, MSN_LIST_AL); + msn_user_unset_op(user, MSN_LIST_AL_OP); } if (user->networkid != MSN_NETWORK_UNKNOWN) { @@ -840,17 +840,48 @@ MsnSession *session; PurpleAccount *account; PurpleConnection *gc; - char *adl = g_strndup(payload, len); - char *reason = g_strdup_printf(_("Unknown error (%d): %s"), - GPOINTER_TO_INT(cmd->payload_cbdata), adl); - g_free(adl); + int error = GPOINTER_TO_INT(cmd->payload_cbdata); session = cmdproc->session; account = session->account; gc = purple_account_get_connection(account); - purple_notify_error(gc, NULL, _("Unable to add user"), reason); - g_free(reason); + if (error == 241) { + /* khc: some googling suggests that error 241 means the buddy is somehow + in the local list, but not the server list, and that we should add + those buddies to the addressbook. For now I will just notify the user + about the raw payload, because I am lazy */ + xmlnode *adl = xmlnode_from_str(payload, len); + GString *emails = g_string_new(NULL); + + xmlnode *domain = xmlnode_get_child(adl, "d"); + while (domain) { + const char *domain_str = xmlnode_get_attrib(domain, "n"); + xmlnode *contact = xmlnode_get_child(domain, "c"); + while (contact) { + g_string_append_printf(emails, "%s@%s\n", + xmlnode_get_attrib(contact, "n"), domain_str); + contact = xmlnode_get_next_twin(contact); + } + domain = xmlnode_get_next_twin(domain); + } + + purple_notify_error(gc, NULL, + _("The following users are missing from your addressbook"), + emails->str); + g_string_free(emails, TRUE); + xmlnode_free(adl); + } + else + { + char *adl = g_strndup(payload, len); + char *reason = g_strdup_printf(_("Unknown error (%d): %s"), + error, adl); + g_free(adl); + + purple_notify_error(gc, NULL, _("Unable to add user"), reason); + g_free(reason); + } } static void @@ -878,50 +909,49 @@ } static void -adl_241_error_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, - size_t len) +rml_error_parse(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len) { - /* khc: some googling suggests that error 241 means the buddy is somehow - in the local list, but not the server list, and that we should add - those buddies to the addressbook. For now I will just notify the user - about the raw payload, because I am lazy */ MsnSession *session; PurpleAccount *account; PurpleConnection *gc; - xmlnode *adl; - xmlnode *domain; - GString *emails; + char *adl, *reason; + int error = GPOINTER_TO_INT(cmd->payload_cbdata); session = cmdproc->session; account = session->account; gc = purple_account_get_connection(account); - adl = xmlnode_from_str(payload, len); - emails = g_string_new(NULL); + adl = g_strndup(payload, len); + reason = g_strdup_printf(_("Unknown error (%d): %s"), + error, adl); + g_free(adl); - domain = xmlnode_get_child(adl, "d"); - while (domain) { - const char *domain_str = xmlnode_get_attrib(domain, "n"); - xmlnode *contact = xmlnode_get_child(domain, "c"); - while (contact) { - g_string_append_printf(emails, "%s@%s\n", - xmlnode_get_attrib(contact, "n"), domain_str); - contact = xmlnode_get_next_twin(contact); - } - domain = xmlnode_get_next_twin(domain); - } - - purple_notify_error(gc, NULL, - _("The following users are missing from your addressbook"), emails->str); - g_string_free(emails, TRUE); - xmlnode_free(adl); + purple_notify_error(gc, NULL, _("Unable to remove user"), reason); + g_free(reason); } static void -adl_241_error_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +rml_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) { - cmdproc->last_cmd->payload_cb = adl_241_error_cmd_post; - cmd->payload_len = atoi(cmd->params[1]); + MsnSession *session; + PurpleAccount *account; + PurpleConnection *gc; + MsnCommand *cmd = cmdproc->last_cmd; + + session = cmdproc->session; + account = session->account; + gc = purple_account_get_connection(account); + + purple_debug_error("msn", "RML error\n"); + if (cmd->param_count > 1) { + cmd->payload_cb = rml_error_parse; + cmd->payload_len = atoi(cmd->params[1]); + cmd->payload_cbdata = GINT_TO_POINTER(error); + } else { + char *reason = g_strdup_printf(_("Unknown error (%d)"), error); + purple_notify_error(gc, NULL, _("Unable to remove user"), reason); + g_free(reason); + } } static void @@ -1068,7 +1098,17 @@ /* Where'd this come from? */ return; - if (cmd->param_count == 7) { + if (cmd->param_count == 8) { + /* Yahoo! Buddy, looks like */ + networkid = atoi(cmd->params[3]); + friendly = g_strdup(purple_url_decode(cmd->params[4])); + clientid = strtoul(cmd->params[5], NULL, 10); + + /* cmd->params[7] seems to be a URL to a Yahoo! icon: + https://sec.yimg.com/i/us/nt/b/purpley.1.0.png + ... and it's purple, HAH! + */ + } else if (cmd->param_count == 7) { /* MSNP14+ with Display Picture object */ networkid = atoi(cmd->params[3]); friendly = g_strdup(purple_url_decode(cmd->params[4])); @@ -2095,9 +2135,8 @@ msn_table_add_cmd(cbs_table, "fallback", "XFR", xfr_cmd); - msn_table_add_cmd(cbs_table, NULL, "241", adl_241_error_cmd); - msn_table_add_error(cbs_table, "ADL", adl_error); + msn_table_add_error(cbs_table, "RML", rml_error); msn_table_add_error(cbs_table, "FQY", fqy_error); msn_table_add_error(cbs_table, "USR", usr_error); diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/msn/oim.c --- a/libpurple/protocols/msn/oim.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/msn/oim.c Mon Nov 09 01:42:24 2009 +0000 @@ -153,7 +153,7 @@ gpointer cb_data; } MsnOimRequestData; -static void msn_oim_request_helper(MsnOimRequestData *data); +static gboolean msn_oim_request_helper(MsnOimRequestData *data); static void msn_oim_request_cb(MsnSoapMessage *request, MsnSoapMessage *response, @@ -202,7 +202,7 @@ g_free(data); } -static void +static gboolean msn_oim_request_helper(MsnOimRequestData *data) { MsnSession *session = data->oim->session; @@ -224,13 +224,13 @@ const char *msn_p; token = msn_nexus_get_token(session->nexus, MSN_AUTH_MESSENGER_WEB); - g_return_if_fail(token != NULL); + g_return_val_if_fail(token != NULL, FALSE); msn_t = g_hash_table_lookup(token, "t"); msn_p = g_hash_table_lookup(token, "p"); - g_return_if_fail(msn_t != NULL); - g_return_if_fail(msn_p != NULL); + g_return_val_if_fail(msn_t != NULL, FALSE); + g_return_val_if_fail(msn_p != NULL, FALSE); passport = xmlnode_get_child(data->body, "Header/PassportCookie"); xml_t = xmlnode_get_child(passport, "t"); @@ -248,6 +248,8 @@ msn_soap_message_new(data->action, xmlnode_copy(data->body)), data->host, data->url, FALSE, msn_oim_request_cb, data); + + return FALSE; } diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/msn/userlist.c --- a/libpurple/protocols/msn/userlist.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/msn/userlist.c Mon Nov 09 01:42:24 2009 +0000 @@ -539,7 +539,7 @@ purple_debug_info("msn", "Add user: %s to group: %s\n", who, new_group_name); - if (!purple_email_is_valid(who)) + if (!msn_email_is_valid(who)) { /* only notify the user about problems adding to the friends list * maybe we should do something else for other lists, but it probably diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/Makefile.am Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/Makefile.mingw --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/Makefile.mingw Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/actions.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/actions.c Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/actions.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/actions.h Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/aes.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/aes.c Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/aes.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/aes.h Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/chunk.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/chunk.c Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/chunk.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/chunk.h Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/cipher.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/cipher.c Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/cipher.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/cipher.h Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/filexfer.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/filexfer.c Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/filexfer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/filexfer.h Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/formcmds.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/formcmds.c Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/formcmds.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/formcmds.h Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/http.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/http.c Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/http.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/http.h Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/login.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/login.c Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/login.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/login.h Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/markup.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/markup.c Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/markup.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/markup.h Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/multimx.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/multimx.h Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/mxit.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/mxit.c Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/mxit.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/mxit.h Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/profile.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/profile.c Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/profile.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/profile.h Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/protocol.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/protocol.c Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/protocol.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/protocol.h Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/roster.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/roster.c Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/roster.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/roster.h Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/splashscreen.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/splashscreen.c Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/mxit/splashscreen.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/splashscreen.h Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 libpurple/protocols/oscar/clientlogin.c --- a/libpurple/protocols/oscar/clientlogin.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/oscar/clientlogin.c Mon Nov 09 01:42:24 2009 +0000 @@ -40,6 +40,7 @@ #include "core.h" #include "oscar.h" +#include "oscarcommon.h" #define URL_CLIENT_LOGIN "https://api.screenname.aol.com/auth/clientLogin" #define URL_START_OSCAR_SESSION "http://api.oscar.aol.com/aim/startOSCARSession" @@ -102,11 +103,15 @@ return signature; } -static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **host, unsigned short *port, char **cookie) +static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **host, unsigned short *port, char **cookie, char **tls_certname) { xmlnode *response_node, *tmp_node, *data_node; - xmlnode *host_node = NULL, *port_node = NULL, *cookie_node = NULL; + xmlnode *host_node = NULL, *port_node = NULL, *cookie_node = NULL, *tls_node = NULL; + gboolean use_tls; char *tmp; + guint code; + + use_tls = purple_account_get_bool(purple_connection_get_account(gc), "use_ssl", OSCAR_DEFAULT_USE_SSL); /* Parse the response as XML */ response_node = xmlnode_from_str(response, response_len); @@ -131,6 +136,7 @@ host_node = xmlnode_get_child(data_node, "host"); port_node = xmlnode_get_child(data_node, "port"); cookie_node = xmlnode_get_child(data_node, "cookie"); + tls_node = xmlnode_get_child(data_node, "tlsCertName"); } /* Make sure we have a status code */ @@ -148,12 +154,13 @@ } /* Make sure the status code was 200 */ - if (strcmp(tmp, "200") != 0) + code = atoi(tmp); + if (code != 200) { purple_debug_error("oscar", "startOSCARSession response statusCode " "was %s: %s\n", tmp, response); - if (strcmp(tmp, "401") == 0) + if (code == 401 || code == 607) purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("You have been connecting and disconnecting too " @@ -177,7 +184,8 @@ /* Make sure we have everything else */ if (data_node == NULL || host_node == NULL || - port_node == NULL || cookie_node == NULL) + port_node == NULL || cookie_node == NULL || + (use_tls && tls_node == NULL)) { char *msg; purple_debug_error("oscar", "startOSCARSession response was missing " @@ -195,7 +203,12 @@ *host = xmlnode_get_data_unescaped(host_node); tmp = xmlnode_get_data_unescaped(port_node); *cookie = xmlnode_get_data_unescaped(cookie_node); - if (*host == NULL || **host == '\0' || tmp == NULL || *tmp == '\0' || cookie == NULL || *cookie == '\0') + + if (use_tls) + *tls_certname = xmlnode_get_data_unescaped(tls_node); + + if (*host == NULL || **host == '\0' || tmp == NULL || *tmp == '\0' || *cookie == NULL || **cookie == '\0' || + (use_tls && (*tls_certname == NULL || **tls_certname == '\0'))) { char *msg; purple_debug_error("oscar", "startOSCARSession response was missing " @@ -223,6 +236,7 @@ OscarData *od; PurpleConnection *gc; char *host, *cookie; + char *tls_certname = NULL; unsigned short port; guint8 *cookiedata; gsize cookiedata_len; @@ -244,28 +258,30 @@ return; } - if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie)) + if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie, &tls_certname)) return; cookiedata = purple_base64_decode(cookie, &cookiedata_len); - oscar_connect_to_bos(gc, od, host, port, cookiedata, cookiedata_len); + oscar_connect_to_bos(gc, od, host, port, cookiedata, cookiedata_len, tls_certname); g_free(cookiedata); g_free(host); g_free(cookie); + g_free(tls_certname); } static void send_start_oscar_session(OscarData *od, const char *token, const char *session_key, time_t hosttime) { char *query_string, *signature, *url; + gboolean use_tls = purple_account_get_bool(purple_connection_get_account(od->gc), "use_ssl", OSCAR_DEFAULT_USE_SSL); /* Construct the GET parameters */ query_string = g_strdup_printf("a=%s" "&f=xml" "&k=%s" "&ts=%" PURPLE_TIME_T_MODIFIER - "&useTLS=0", - purple_url_encode(token), get_client_key(od), hosttime); + "&useTLS=%d", + purple_url_encode(token), get_client_key(od), hosttime, use_tls); signature = generate_signature("GET", URL_START_OSCAR_SESSION, query_string, session_key); url = g_strdup_printf(URL_START_OSCAR_SESSION "?%s&sig_sha256=%s", diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/oscar/family_feedbag.c --- a/libpurple/protocols/oscar/family_feedbag.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/oscar/family_feedbag.c Mon Nov 09 01:42:24 2009 +0000 @@ -389,11 +389,10 @@ /** * Locally find the presence flag item, and return the setting. The returned setting is a - * bitmask of the user flags that you are visible to. See the AIM_FLAG_* #defines - * in oscar.h + * bitmask of the preferences. See the AIM_SSI_PRESENCE_FLAG_* #defines in oscar.h. * * @param list A pointer to the current list of items. - * @return Return the current visibility mask. + * @return Return the current set of preferences. */ guint32 aim_ssi_getpresence(struct aim_ssi_item *list) { @@ -1130,9 +1129,11 @@ * should show up as idle or not, etc. * * @param od The oscar odion. - * @param presence I think it's a bitmask, but I only know what one of the bits is: - * 0x00000002 - Hide wireless? + * @param presence A bitmask of the first 32 entries [0-31] from + * http://dev.aol.com/aim/oscar/#FEEDBAG__BUDDY_PREFS + * 0x00000002 - Hide "eBuddy group" (whatever that is) * 0x00000400 - Allow others to see your idle time + * 0x00020000 - Don't show Recent Buddies * @return Return 0 if no errors, otherwise return the error number. */ int aim_ssi_setpresence(OscarData *od, guint32 presence) { diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/oscar/family_icbm.c --- a/libpurple/protocols/oscar/family_icbm.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/oscar/family_icbm.c Mon Nov 09 01:42:24 2009 +0000 @@ -151,6 +151,55 @@ return AIM_CLIENTTYPE_UNKNOWN; } +/* + * Subtype 0x0001 - Error + */ +static int +error(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) +{ + int ret = 0; + aim_rxcallback_t userfunc; + aim_snac_t *snac2; + guint16 reason, errcode = 0; + char *bn; + GSList *tlvlist; + + if (!(snac2 = aim_remsnac(od, snac->id))) { + purple_debug_misc("oscar", "icbm error: received response from unknown request!\n"); + return 0; + } + + if (snac2->family != SNAC_FAMILY_ICBM) { + purple_debug_misc("oscar", "icbm error: received response from invalid request! %d\n", snac2->family); + g_free(snac2->data); + g_free(snac2); + return 0; + } + + if (!(bn = snac2->data)) { + purple_debug_misc("oscar", "icbm error: received response from request without a buddy name!\n"); + g_free(snac2); + return 0; + } + + reason = byte_stream_get16(bs); + + tlvlist = aim_tlvlist_read(bs); + if (aim_tlv_gettlv(tlvlist, 0x0008, 1)) + errcode = aim_tlv_get16(tlvlist, 0x0008, 1); + aim_tlvlist_free(tlvlist); + + /* Notify the user that the message wasn't delivered */ + if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) + ret = userfunc(od, conn, frame, reason, errcode, bn); + + if (snac2) + g_free(snac2->data); + g_free(snac2); + + return ret; +} + /** * Subtype 0x0002 - Set ICBM parameters. * @@ -2789,7 +2838,9 @@ static int snachandler(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { - if (snac->subtype == 0x0005) + if (snac->subtype == 0x0001) + return error(od, conn, mod, frame, snac, bs); + else if (snac->subtype == 0x0005) return aim_im_paraminfo(od, conn, mod, frame, snac, bs); else if (snac->subtype == 0x0006) return outgoingim(od, conn, mod, frame, snac, bs); diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/oscar/family_oservice.c --- a/libpurple/protocols/oscar/family_oservice.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/oscar/family_oservice.c Mon Nov 09 01:42:24 2009 +0000 @@ -319,7 +319,10 @@ for (i = 0; i < numclasses; i++) { struct rateclass *rateclass; + guint32 delta; + struct timeval now; + gettimeofday(&now, NULL); rateclass = g_new0(struct rateclass, 1); rateclass->classid = byte_stream_get16(bs); @@ -339,11 +342,24 @@ * the new version hardcoded here. */ if (mod->version >= 3) - byte_stream_getrawbuf(bs, rateclass->unknown, sizeof(rateclass->unknown)); + { + rateclass->delta = byte_stream_get32(bs); + rateclass->dropping_snacs = byte_stream_get8(bs); + + delta = rateclass->delta; + + rateclass->last.tv_sec = now.tv_sec - delta / 1000; + delta %= 1000; + rateclass->last.tv_usec = now.tv_usec - delta * 1000; + } + else + { + rateclass->delta = rateclass->dropping_snacs = 0; + rateclass->last.tv_sec = now.tv_sec; + rateclass->last.tv_usec = now.tv_usec; + } rateclass->members = g_hash_table_new(g_direct_hash, g_direct_equal); - rateclass->last.tv_sec = 0; - rateclass->last.tv_usec = 0; conn->rateclasses = g_slist_prepend(conn->rateclasses, rateclass); } conn->rateclasses = g_slist_reverse(conn->rateclasses); @@ -383,8 +399,7 @@ */ /* - * Last step in the conn init procedure is to acknowledge that we - * agree to these draconian limitations. + * Subscribe to rate change information for all rate classes. */ aim_srv_rates_addparam(od, conn); @@ -451,7 +466,10 @@ aim_rxcallback_t userfunc; guint16 code, classid; struct rateclass *rateclass; + guint32 delta; + struct timeval now; + gettimeofday(&now, NULL); code = byte_stream_get16(bs); classid = byte_stream_get16(bs); @@ -468,8 +486,29 @@ rateclass->current = byte_stream_get32(bs); rateclass->max = byte_stream_get32(bs); - if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) - ret = userfunc(od, conn, frame, code, classid, rateclass->windowsize, rateclass->clear, rateclass->alert, rateclass->limit, rateclass->disconnect, rateclass->current, rateclass->max); + if (mod->version >= 3) + { + rateclass->delta = byte_stream_get32(bs); + rateclass->dropping_snacs = byte_stream_get8(bs); + + delta = rateclass->delta; + + rateclass->last.tv_sec = now.tv_sec - delta / 1000; + delta %= 1000; + rateclass->last.tv_usec = now.tv_usec - delta * 1000; + } + else + { + rateclass->delta = rateclass->dropping_snacs = 0; + rateclass->last.tv_sec = now.tv_sec; + rateclass->last.tv_usec = now.tv_usec; + } + + if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) { + /* Can't pass in guint8 via ... varargs, so we use an unsigned int */ + unsigned int dropping_snacs = rateclass->dropping_snacs; + ret = userfunc(od, conn, frame, code, classid, rateclass->windowsize, rateclass->clear, rateclass->alert, rateclass->limit, rateclass->disconnect, rateclass->current, rateclass->max, rateclass->delta, dropping_snacs); + } return ret; } diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/oscar/flap_connection.c --- a/libpurple/protocols/oscar/flap_connection.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/oscar/flap_connection.c Mon Nov 09 01:42:24 2009 +0000 @@ -73,7 +73,7 @@ } void -flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci) +flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci, gboolean allow_multiple_logins) { FlapFrame *frame; GSList *tlvlist = NULL; @@ -94,7 +94,7 @@ aim_tlvlist_add_16(&tlvlist, 0x0018, (guint16)ci->minor); aim_tlvlist_add_16(&tlvlist, 0x0019, (guint16)ci->point); aim_tlvlist_add_16(&tlvlist, 0x001a, (guint16)ci->build); - aim_tlvlist_add_8(&tlvlist, 0x004a, 0x01); + aim_tlvlist_add_8(&tlvlist, 0x004a, (allow_multiple_logins ? 0x01 : 0x03)); aim_tlvlist_write(&frame->data, &tlvlist); @@ -131,11 +131,13 @@ rateclass_get_new_current(FlapConnection *conn, struct rateclass *rateclass, struct timeval *now) { unsigned long timediff; /* In milliseconds */ + guint32 current; timediff = (now->tv_sec - rateclass->last.tv_sec) * 1000 + (now->tv_usec - rateclass->last.tv_usec) / 1000; + current = ((rateclass->current * (rateclass->windowsize - 1)) + timediff) / rateclass->windowsize; - /* This formula is taken from the joscar API docs. Preesh. */ - return MIN(((rateclass->current * (rateclass->windowsize - 1)) + timediff) / rateclass->windowsize, rateclass->max); + /* This formula is taken from http://dev.aol.com/aim/oscar/#RATELIMIT */ + return MIN(current, rateclass->max); } /* @@ -161,8 +163,7 @@ new_current = rateclass_get_new_current(conn, rateclass, &now); - /* (Add 100ms padding to account for inaccuracies in the calculation) */ - if (new_current < rateclass->alert + 100) + if (rateclass->dropping_snacs || new_current <= rateclass->alert) /* Not ready to send this SNAC yet--keep waiting. */ return FALSE; @@ -245,10 +246,9 @@ gettimeofday(&now, NULL); new_current = rateclass_get_new_current(conn, rateclass, &now); - /* (Add 100ms padding to account for inaccuracies in the calculation) */ - if (new_current < rateclass->alert + 100) + if (rateclass->dropping_snacs || new_current <= rateclass->alert) { - purple_debug_info("oscar", "Current rate for conn %p would be %u, but we alert at %u; enqueueing\n", conn, new_current, (rateclass->alert + 100)); + purple_debug_info("oscar", "Current rate for conn %p would be %u, but we alert at %u; enqueueing\n", conn, new_current, rateclass->alert); enqueue = TRUE; } diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/oscar/libaim.c --- a/libpurple/protocols/oscar/libaim.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/oscar/libaim.c Mon Nov 09 01:42:24 2009 +0000 @@ -141,7 +141,7 @@ static void init_plugin(PurplePlugin *plugin) { - oscar_init(PURPLE_PLUGIN_PROTOCOL_INFO(plugin)); + oscar_init(plugin); } PURPLE_INIT_PLUGIN(aim, init_plugin, info); diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/oscar/libicq.c --- a/libpurple/protocols/oscar/libicq.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/oscar/libicq.c Mon Nov 09 01:42:24 2009 +0000 @@ -153,7 +153,7 @@ { PurpleAccountOption *option; - oscar_init(PURPLE_PLUGIN_PROTOCOL_INFO(plugin)); + oscar_init(plugin); option = purple_account_option_string_new(_("Encoding"), "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/oscar/oscar.c --- a/libpurple/protocols/oscar/oscar.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/oscar/oscar.c Mon Nov 09 01:42:24 2009 +0000 @@ -144,6 +144,26 @@ }; static const int msgerrreasonlen = G_N_ELEMENTS(msgerrreason); +static const char * const errcodereason[] = { + N_("Invalid error"), + N_("Not logged in"), + N_("Cannot receive IM due to parental controls"), + N_("Cannot send SMS without accepting terms"), + N_("Cannot send SMS"), /* SMS_WITHOUT_DISCLAIMER is weird */ + N_("Cannot send SMS to this country"), + N_("Unknown error"), /* Undocumented */ + N_("Unknown error"), /* Undocumented */ + N_("Cannot send SMS to unknown country"), + N_("Bot accounts cannot initiate IMs"), + N_("Bot account cannot IM this user"), + N_("Bot account reached IM limit"), + N_("Bot account reached daily IM limit"), + N_("Bot account reached monthly IM limit"), + N_("Unable to receive offline messages"), + N_("Offline message store full") +}; +static const int errcodereasonlen = G_N_ELEMENTS(errcodereason); + /* All the libfaim->purple callback functions */ /* Only used when connecting with the old-style BUCP login */ @@ -1168,7 +1188,8 @@ ClientInfo icqinfo = CLIENTINFO_PURPLE_ICQ; flap_connection_send_version_with_cookie_and_clientinfo(od, conn, conn->cookielen, conn->cookie, - od->icq ? &icqinfo : &aiminfo); + od->icq ? &icqinfo : &aiminfo, + purple_account_get_bool(account, "allow_multiple_logins", OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS)); } else { flap_connection_send_version_with_cookie(od, conn, conn->cookielen, conn->cookie); @@ -1394,9 +1415,9 @@ presence = aim_ssi_getpresence(od->ssi.local); if (report_idle) - aim_ssi_setpresence(od, presence | 0x400); + aim_ssi_setpresence(od, presence | AIM_SSI_PRESENCE_FLAG_SHOWIDLE); else - aim_ssi_setpresence(od, presence & ~0x400); + aim_ssi_setpresence(od, presence & ~AIM_SSI_PRESENCE_FLAG_SHOWIDLE); } /** @@ -1806,17 +1827,35 @@ return 1; } -int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen) +int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen, const char *tls_certname) { + PurpleAccount *account; FlapConnection *conn; + account = purple_connection_get_account(gc); + conn = flap_connection_new(od, SNAC_FAMILY_LOCATE); conn->cookielen = cookielen; conn->cookie = g_memdup(cookie, cookielen); - conn->connect_data = purple_proxy_connect(NULL, - purple_connection_get_account(gc), host, port, - connection_established_cb, conn); - if (conn->connect_data == NULL) + + /* + * tls_certname is only set (and must be set if we get this far) if + * SSL is enabled. + */ + if (tls_certname) + { + conn->gsc = purple_ssl_connect_with_ssl_cn(account, host, port, + ssl_connection_established_cb, ssl_connection_error_cb, + tls_certname, conn); + } + else + { + conn->connect_data = purple_proxy_connect(NULL, + account, host, port, + connection_established_cb, conn); + } + + if (conn->gsc == NULL && conn->connect_data == NULL) { purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect")); return 0; @@ -1877,7 +1916,7 @@ break; case 0x18: /* username connecting too frequently */ - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("Your username has been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer.")); break; case 0x1c: { @@ -1889,7 +1928,7 @@ } case 0x1d: /* IP address connecting too frequently */ - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("You have been connecting and disconnecting too frequently. Wait a minute and try again. If you continue to try, you will need to wait even longer.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("Your IP address has been connecting and disconnecting too frequently. Wait a minute and try again. If you continue to try, you will need to wait even longer.")); break; default: purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Unknown reason")); @@ -2874,25 +2913,46 @@ gchar **text; text = g_strsplit(args->msg, "\376", 0); if (text) { - num = 0; - for (i=0; iuin, text[i*2+2], text[i*2+1]); - data->gc = gc; - data->name = g_strdup(text[i*2+1]); - data->nick = g_strdup(text[i*2+2]); - - purple_request_action(gc, NULL, message, - _("Do you want to add this buddy " - "to your buddy list?"), - PURPLE_DEFAULT_ACTION_NONE, - purple_connection_get_account(gc), data->name, NULL, - data, 2, - _("_Add"), G_CALLBACK(purple_icq_buddyadd), - _("_Decline"), G_CALLBACK(oscar_free_name_data)); - g_free(message); + /* Read the number of contacts that we were sent */ + errno = 0; + num = text[0] ? strtoul(text[0], NULL, 10) : 0; + + if (num > 0 && errno == 0) { + for (i=0; imsg, NULL); + purple_debug_error("oscar", "Unknown syntax parsing " + "ICQ buddies. args->msg=%s\n", tmp); + g_free(tmp); + break; + } + + message = g_strdup_printf(_("ICQ user %u has sent you a buddy: %s (%s)"), args->uin, text[i*2+2], text[i*2+1]); + + data = g_new(struct name_data, 1); + data->gc = gc; + data->name = g_strdup(text[i*2+1]); + data->nick = g_strdup(text[i*2+2]); + + purple_request_action(gc, NULL, message, + _("Do you want to add this buddy " + "to your buddy list?"), + PURPLE_DEFAULT_ACTION_NONE, + purple_connection_get_account(gc), data->name, NULL, + data, 2, + _("_Add"), G_CALLBACK(purple_icq_buddyadd), + _("_Decline"), G_CALLBACK(oscar_free_name_data)); + g_free(message); + } + } else { + gchar *tmp = g_strescape(args->msg, NULL); + purple_debug_error("oscar", "Unknown syntax parsing " + "ICQ buddies. args->msg=%s\n", tmp); + g_free(tmp); } g_strfreev(text); } @@ -3196,17 +3256,18 @@ PurpleXfer *xfer; #endif va_list ap; - guint16 reason; - char *data, *buf; + guint16 reason, errcode; + char *data, *reason_str, *buf; va_start(ap, fr); reason = (guint16)va_arg(ap, unsigned int); + errcode = (guint16)va_arg(ap, unsigned int); data = va_arg(ap, char *); va_end(ap); purple_debug_error("oscar", - "Message error with data %s and reason %hu\n", - (data != NULL ? data : ""), reason); + "Message error with data %s and reason %hu and errcode %hu\n", + (data != NULL ? data : ""), reason, errcode); if ((data == NULL) || (*data == '\0')) /* We can't do anything if data is empty */ @@ -3221,14 +3282,27 @@ #endif /* Data is assumed to be the destination bn */ - buf = g_strdup_printf(_("Unable to send message: %s"), (reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason.")); + + reason_str = g_strdup((reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason")); + if (errcode != 0 && errcode < errcodereasonlen) + buf = g_strdup_printf(_("Unable to send message: %s (%s)"), reason_str, + _(errcodereason[errcode])); + else + buf = g_strdup_printf(_("Unable to send message: %s"), reason_str); + if (!purple_conv_present_error(data, purple_connection_get_account(gc), buf)) { g_free(buf); - buf = g_strdup_printf(_("Unable to send message to %s:"), data ? data : "(unknown)"); - purple_notify_error(od->gc, NULL, buf, - (reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason.")); + if (errcode != 0 && errcode < errcodereasonlen) + buf = g_strdup_printf(_("Unable to send message to %s: %s (%s)"), + data ? data : "(unknown)", reason_str, + _(errcodereason[errcode])); + else + buf = g_strdup_printf(_("Unable to send message to %s: %s"), + data ? data : "(unknown)", reason_str); + purple_notify_error(od->gc, NULL, buf, reason_str); } g_free(buf); + g_free(reason_str); return 1; } @@ -3719,7 +3793,8 @@ }; va_list ap; guint16 code, rateclass; - guint32 windowsize, clear, alert, limit, disconnect, currentavg, maxavg; + guint32 windowsize, clear, alert, limit, disconnect, currentavg, maxavg, delta; + guint8 dropping_snacs; va_start(ap, fr); code = (guint16)va_arg(ap, unsigned int); @@ -3731,23 +3806,28 @@ disconnect = va_arg(ap, guint32); currentavg = va_arg(ap, guint32); maxavg = va_arg(ap, guint32); + delta = va_arg(ap, guint32); + dropping_snacs = (guint8)va_arg(ap, unsigned int); va_end(ap); purple_debug_misc("oscar", "rate %s (param ID 0x%04hx): curavg = %u, maxavg = %u, alert at %u, " - "clear warning at %u, limit at %u, disconnect at %u (window size = %u)\n", + "clear warning at %u, limit at %u, disconnect at %u, delta is %u, dropping is %u (window size = %u)\n", (code < 5) ? codes[code] : codes[0], rateclass, currentavg, maxavg, alert, clear, limit, disconnect, - windowsize); + delta, + dropping_snacs, + windowsize + ); if (code == AIM_RATE_CODE_LIMIT) { purple_debug_warning("oscar", _("The last action you attempted could not be " "performed because you are over the rate limit. " - "Please wait 10 seconds and try again.")); + "Please wait 10 seconds and try again.\n")); } return 1; @@ -3909,12 +3989,8 @@ od->rights.maxpermits = (guint)maxpermits; od->rights.maxdenies = (guint)maxdenies; - purple_connection_set_state(gc, PURPLE_CONNECTED); - purple_debug_info("oscar", "buddy list loaded\n"); - aim_srv_clientready(od, conn); - if (purple_account_get_user_info(account) != NULL) serv_set_info(gc, purple_account_get_user_info(account)); @@ -3941,9 +4017,6 @@ presence = purple_status_get_presence(status); aim_srv_setidle(od, !purple_presence_is_idle(presence) ? 0 : time(NULL) - purple_presence_get_idle_time(presence)); - /* Request offline messages for AIM and ICQ */ - aim_im_reqofflinemsgs(od); - if (od->icq) { #ifdef OLDSTYLE_ICQ_OFFLINEMSGS aim_icq_reqofflinemsgs(od); @@ -3957,6 +4030,26 @@ aim_srv_requestnew(od, SNAC_FAMILY_ALERT); aim_srv_requestnew(od, SNAC_FAMILY_CHATNAV); + od->bos.have_rights = TRUE; + + /* + * If we've already received our feedbag data then we're not waiting on + * anything else, so send the server clientready. + * + * Normally we get bos rights before we get our feedbag data, so this + * rarely (never?) happens. And I'm not sure it actually matters if we + * wait for bos rights before calling clientready. But it seems safer + * to do it this way. + */ + if (od->ssi.received_data) { + aim_srv_clientready(od, conn); + + /* Request offline messages for AIM and ICQ */ + aim_im_reqofflinemsgs(od); + + purple_connection_set_state(gc, PURPLE_CONNECTED); + } + return 1; } @@ -5151,7 +5244,7 @@ { /* If not in server list then prune from local list */ GSList *cur, *next; GSList *buddies = purple_find_buddies(account, NULL); - + /* Buddies */ cur = NULL; @@ -5231,9 +5324,9 @@ report_idle = strcmp(idle_reporting_pref, "none") != 0; if (report_idle) - aim_ssi_setpresence(od, tmp | 0x400); + aim_ssi_setpresence(od, tmp | AIM_SSI_PRESENCE_FLAG_SHOWIDLE); else - aim_ssi_setpresence(od, tmp & ~0x400); + aim_ssi_setpresence(od, tmp & ~AIM_SSI_PRESENCE_FLAG_SHOWIDLE); } @@ -5241,45 +5334,28 @@ /* Add from server list to local list */ for (curitem=od->ssi.local; curitem; curitem=curitem->next) { - if ((curitem->name == NULL) || (g_utf8_validate(curitem->name, -1, NULL))) switch (curitem->type) { - case 0x0000: { /* Buddy */ + case AIM_SSI_TYPE_BUDDY: { /* Buddy */ if (curitem->name) { struct aim_ssi_item *groupitem; - char *gname, *gname_utf8, *alias, *alias_utf8; + const char *gname, *alias; groupitem = aim_ssi_itemlist_find(od->ssi.local, curitem->gid, 0x0000); gname = groupitem ? groupitem->name : NULL; - if (gname != NULL) { - if (g_utf8_validate(gname, -1, NULL)) - gname_utf8 = g_strdup(gname); - else - gname_utf8 = oscar_utf8_try_convert(account, gname); - } else - gname_utf8 = NULL; - - g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans")); + + g = purple_find_group(gname ? gname : _("Orphans")); if (g == NULL) { - g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans")); + g = purple_group_new(gname ? gname : _("Orphans")); purple_blist_add_group(g, NULL); } alias = aim_ssi_getalias(od->ssi.local, gname, curitem->name); - if (alias != NULL) { - if (g_utf8_validate(alias, -1, NULL)) - alias_utf8 = g_strdup(alias); - else - alias_utf8 = oscar_utf8_try_convert(account, alias); - g_free(alias); - } else - alias_utf8 = NULL; - b = purple_find_buddy_in_group(account, curitem->name, g); if (b) { /* Get server stored alias */ - purple_blist_alias_buddy(b, alias_utf8); + purple_blist_alias_buddy(b, alias); } else { - b = purple_buddy_new(account, curitem->name, alias_utf8); + b = purple_buddy_new(account, curitem->name, alias); purple_debug_info("oscar", "ssi: adding buddy %s to group %s to local list\n", curitem->name, gname); @@ -5303,33 +5379,18 @@ purple_buddy_get_name(b), OSCAR_STATUS_ID_MOBILE, NULL); } - - g_free(gname_utf8); - g_free(alias_utf8); } } break; - case 0x0001: { /* Group */ - char *gname; - char *gname_utf8; - - gname = curitem->name; - if (gname != NULL) { - if (g_utf8_validate(gname, -1, NULL)) - gname_utf8 = g_strdup(gname); - else - gname_utf8 = oscar_utf8_try_convert(account, gname); - } else - gname_utf8 = NULL; - - if (gname_utf8 != NULL && purple_find_group(gname_utf8) == NULL) { - g = purple_group_new(gname_utf8); + case AIM_SSI_TYPE_GROUP: { /* Group */ + const char *gname = curitem->name; + if (gname != NULL && purple_find_group(gname) == NULL) { + g = purple_group_new(gname); purple_blist_add_group(g, NULL); } - g_free(gname_utf8); } break; - case 0x0002: { /* Permit buddy */ + case AIM_SSI_TYPE_PERMIT: { /* Permit buddy */ if (curitem->name) { /* if (!find_permdeny_by_name(gc->permit, curitem->name)) { AAA */ GSList *list; @@ -5342,7 +5403,7 @@ } } break; - case 0x0003: { /* Deny buddy */ + case AIM_SSI_TYPE_DENY: { /* Deny buddy */ if (curitem->name) { GSList *list; for (list=account->deny; (list && oscar_util_name_compare(curitem->name, list->data)); list=list->next); @@ -5354,7 +5415,7 @@ } } break; - case 0x0004: { /* Permit/deny setting */ + case AIM_SSI_TYPE_PDINFO: { /* Permit/deny setting */ /* * We don't inherit the permit/deny setting from the server * for ICQ because, for ICQ, this setting controls who can @@ -5372,7 +5433,7 @@ } } break; - case 0x0005: { /* Presence setting */ + case AIM_SSI_TYPE_PRESENCEPREFS: { /* Presence setting */ /* We don't want to change Purple's setting because it applies to all accounts */ } break; } /* End of switch on curitem->type */ @@ -5396,6 +5457,19 @@ oscar_set_icon(gc, img); purple_imgstore_unref(img); + /* + * If we've already received our bos rights then we're not waiting on + * anything else, so send the server clientready. + */ + if (od->bos.have_rights) { + aim_srv_clientready(od, conn); + + /* Request offline messages for AIM and ICQ */ + aim_im_reqofflinemsgs(od); + + purple_connection_set_state(gc, PURPLE_CONNECTED); + } + return 1; } @@ -5452,7 +5526,8 @@ { PurpleConnection *gc; PurpleAccount *account; - char *gname, *gname_utf8, *alias, *alias_utf8; + const char *gname; + char *alias; PurpleBuddy *b; PurpleGroup *g; struct aim_ssi_item *ssi_item; @@ -5473,19 +5548,7 @@ return 1; gname = aim_ssi_itemlist_findparentname(od->ssi.local, name); - gname_utf8 = gname ? oscar_utf8_try_convert(account, gname) : NULL; - alias = aim_ssi_getalias(od->ssi.local, gname, name); - if (alias != NULL) - { - if (g_utf8_validate(alias, -1, NULL)) - alias_utf8 = g_strdup(alias); - else - alias_utf8 = oscar_utf8_try_convert(account, alias); - } - else - alias_utf8 = NULL; - g_free(alias); b = purple_find_buddy(account, name); if (b) { @@ -5494,21 +5557,21 @@ * of your buddies, so update our local buddy list with * the person's new alias. */ - purple_blist_alias_buddy(b, alias_utf8); + purple_blist_alias_buddy(b, alias); } else if (snac_subtype == 0x0008) { /* * You're logged in somewhere else and you added a buddy to * your server list, so add them to your local buddy list. */ - b = purple_buddy_new(account, name, alias_utf8); - - if (!(g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans")))) { - g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans")); + b = purple_buddy_new(account, name, alias); + + if (!(g = purple_find_group(gname ? gname : _("Orphans")))) { + g = purple_group_new(gname ? gname : _("Orphans")); purple_blist_add_group(g, NULL); } purple_debug_info("oscar", - "ssi: adding buddy %s to group %s to local list\n", name, gname_utf8 ? gname_utf8 : _("Orphans")); + "ssi: adding buddy %s to group %s to local list\n", name, gname ? gname : _("Orphans")); purple_blist_add_buddy(b, NULL, g, NULL); /* Mobile users should always be online */ @@ -5521,6 +5584,8 @@ } + g_free(alias); + ssi_item = aim_ssi_itemlist_finditem(od->ssi.local, gname, name, AIM_SSI_TYPE_BUDDY); if (ssi_item == NULL) @@ -5530,9 +5595,6 @@ "group %s\n", name, gname); } - g_free(gname_utf8); - g_free(alias_utf8); - return 1; } @@ -6235,7 +6297,6 @@ struct name_data *data; PurpleGroup *g; char *comment; - gchar *comment_utf8; gchar *title; PurpleAccount *account; const char *name; @@ -6254,7 +6315,6 @@ data = g_new(struct name_data, 1); comment = aim_ssi_getcomment(od->ssi.local, purple_group_get_name(g), name); - comment_utf8 = comment ? oscar_utf8_try_convert(account, comment) : NULL; data->gc = gc; data->name = g_strdup(name); @@ -6262,7 +6322,7 @@ title = g_strdup_printf(_("Buddy Comment for %s"), data->name); purple_request_input(gc, title, _("Buddy Comment:"), NULL, - comment_utf8, TRUE, FALSE, NULL, + comment, TRUE, FALSE, NULL, _("_OK"), G_CALLBACK(oscar_ssi_editcomment), _("_Cancel"), G_CALLBACK(oscar_free_name_data), account, data->name, NULL, @@ -6270,7 +6330,6 @@ g_free(title); g_free(comment); - g_free(comment_utf8); } static void @@ -7076,8 +7135,9 @@ return FALSE; } -void oscar_init(PurplePluginProtocolInfo *prpl_info) +void oscar_init(PurplePlugin *plugin) { + PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin); PurpleAccountOption *option; static gboolean init = FALSE; @@ -7100,9 +7160,11 @@ OSCAR_DEFAULT_ALWAYS_USE_RV_PROXY); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); - option = purple_account_option_bool_new(_("Allow multiple simultaneous logins"), "allow_multiple_logins", - OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS); - prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); + if (g_str_equal(purple_plugin_get_id(plugin), "prpl-aim")) { + option = purple_account_option_bool_new(_("Allow multiple simultaneous logins"), "allow_multiple_logins", + OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS); + prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); + } if (init) return; diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/oscar/oscar.h --- a/libpurple/protocols/oscar/oscar.h Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/oscar/oscar.h Mon Nov 09 01:42:24 2009 +0000 @@ -535,6 +535,10 @@ struct aim_userinfo_s *userinfo; } locate; + struct { + gboolean have_rights; + } bos; + /* Server-stored information (ssi) */ struct { gboolean received_data; @@ -619,7 +623,7 @@ } chat; }; -int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen); +int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen, const char *tls_certname); /* family_auth.c */ @@ -657,7 +661,7 @@ void flap_connection_send(FlapConnection *conn, FlapFrame *frame); void flap_connection_send_version(OscarData *od, FlapConnection *conn); void flap_connection_send_version_with_cookie(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy); -void flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci); +void flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci, gboolean allow_multiple_login); void flap_connection_send_snac(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data); void flap_connection_send_snac_with_priority(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data, gboolean high_priority); void flap_connection_send_keepalive(OscarData *od, FlapConnection *conn); @@ -1236,7 +1240,7 @@ #define AIM_SSI_ACK_INVALIDNAME 0x000d #define AIM_SSI_ACK_AUTHREQUIRED 0x000e -/* These flags are set in the 0x00c9 TLV of SSI teyp 0x0005 */ +/* These flags are set in the 0x00c9 TLV of SSI type 0x0005 */ #define AIM_SSI_PRESENCE_FLAG_SHOWIDLE 0x00000400 #define AIM_SSI_PRESENCE_FLAG_NORECENTBUDDIES 0x00020000 @@ -1681,7 +1685,8 @@ guint32 disconnect; guint32 current; guint32 max; - guint8 unknown[5]; /* only present in versions >= 3 */ + guint32 delta; + guint8 dropping_snacs; GHashTable *members; /* Key is family and subtype, value is TRUE. */ struct timeval last; /**< The time when we last sent a SNAC of this rate class. */ diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/oscar/oscarcommon.h --- a/libpurple/protocols/oscar/oscarcommon.h Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/oscar/oscarcommon.h Mon Nov 09 01:42:24 2009 +0000 @@ -94,4 +94,4 @@ gboolean oscar_offline_message(const PurpleBuddy *buddy); void oscar_format_username(PurpleConnection *gc, const char *nick); GList *oscar_actions(PurplePlugin *plugin, gpointer context); -void oscar_init(PurplePluginProtocolInfo *prpl_info); +void oscar_init(PurplePlugin *plugin); diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/silc/Makefile.mingw --- a/libpurple/protocols/silc/Makefile.mingw Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/silc/Makefile.mingw Mon Nov 09 01:42:24 2009 +0000 @@ -7,6 +7,8 @@ PIDGIN_TREE_TOP := ../../.. include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak +DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES)) + TARGET = libsilc NEEDED_DLLS = $(SILC_TOOLKIT)/bin/libsilc-1-1-2.dll \ $(SILC_TOOLKIT)/bin/libsilcclient-1-1-2.dll @@ -79,7 +81,7 @@ $(OBJECTS): $(PURPLE_CONFIG_H) $(TARGET).dll: $(PURPLE_DLL).a $(OBJECTS) - $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--image-base,0x64000000 -o $(TARGET).dll + $(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--image-base,0x74000000 -o $(TARGET).dll ## ## CLEAN RULES diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/silc10/Makefile.mingw --- a/libpurple/protocols/silc10/Makefile.mingw Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/silc10/Makefile.mingw Mon Nov 09 01:42:24 2009 +0000 @@ -7,6 +7,8 @@ PIDGIN_TREE_TOP := ../../.. include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak +DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES)) + TARGET = libsilc NEEDED_DLLS = $(SILC_TOOLKIT)/lib/silc.dll \ $(SILC_TOOLKIT)/lib/silcclient.dll diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/yahoo/libyahoo.c --- a/libpurple/protocols/yahoo/libyahoo.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/yahoo/libyahoo.c Mon Nov 09 01:42:24 2009 +0000 @@ -249,7 +249,7 @@ yahoo_roomlist_get_list, yahoo_roomlist_cancel, yahoo_roomlist_expand_category, - NULL, /* can_receive_file */ + yahoo_can_receive_file, /* can_receive_file */ yahoo_send_file, yahoo_new_xfer, yahoo_offline_message, /* offline_message */ diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/yahoo/libymsg.c --- a/libpurple/protocols/yahoo/libymsg.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/yahoo/libymsg.c Mon Nov 09 01:42:24 2009 +0000 @@ -154,6 +154,7 @@ gboolean unicode = FALSE; char *message = NULL; YahooFederation fed = YAHOO_FEDERATION_NONE; + char *fedname = NULL; if (pkt->service == YAHOO_SERVICE_LOGOFF && pkt->status == -1) { if (!purple_account_get_remember_password(account)) @@ -194,18 +195,20 @@ break; if (p->key == 241) { fed = strtol(p->value, NULL, 10); + g_free(fedname); switch (fed) { case YAHOO_FEDERATION_MSN: - name = g_strconcat("msn/", name, NULL); + name = fedname = g_strconcat("msn/", name, NULL); break; case YAHOO_FEDERATION_OCS: - name = g_strconcat("ocs/", name, NULL); + name = fedname = g_strconcat("ocs/", name, NULL); break; case YAHOO_FEDERATION_IBM: - name = g_strconcat("ibm/", name, NULL); + name = fedname = g_strconcat("ibm/", name, NULL); break; case YAHOO_FEDERATION_NONE: default: + fedname = NULL; break; } break; @@ -390,6 +393,7 @@ yahoo_update_status(gc, name, f); } + g_free(fedname); } static void yahoo_do_group_check(PurpleAccount *account, GHashTable *ht, const char *name, const char *group) @@ -879,6 +883,7 @@ char *id; char *msg; YahooFederation fed; + char *fed_from; }; static void yahoo_process_sms_message(PurpleConnection *gc, struct yahoo_packet *pkt) @@ -950,9 +955,6 @@ GSList *l = pkt->hash; GSList *list = NULL; struct _yahoo_im *im = NULL; - const char *imv = NULL; - gint val_11 = 0; - char *fed_from = NULL; account = purple_connection_get_account(gc); @@ -963,10 +965,11 @@ if (pair->key == 4 || pair->key == 1) { im = g_new0(struct _yahoo_im, 1); list = g_slist_append(list, im); - im->from = fed_from = pair->value; + im->from = pair->value; im->time = time(NULL); im->utf8 = TRUE; im->fed = YAHOO_FEDERATION_NONE; + im->fed_from = g_strdup(im->from); } if (im && pair->key == 5) im->active_id = pair->value; @@ -985,32 +988,75 @@ } if (im && pair->key == 241) { im->fed = strtol(pair->value, NULL, 10); + g_free(im->fed_from); switch (im->fed) { case YAHOO_FEDERATION_MSN: - fed_from = g_strconcat("msn/",im->from, NULL); + im->fed_from = g_strconcat("msn/",im->from, NULL); break; case YAHOO_FEDERATION_OCS: - fed_from = g_strconcat("ocs/",im->from, NULL); + im->fed_from = g_strconcat("ocs/",im->from, NULL); break; case YAHOO_FEDERATION_IBM: - fed_from = g_strconcat("ibm/",im->from, NULL); + im->fed_from = g_strconcat("ibm/",im->from, NULL); break; case YAHOO_FEDERATION_NONE: default: + im->fed_from = g_strdup(im->from); break; } - purple_debug_info("yahoo", "Message from federated (%d) buddy %s.\n", im->fed, fed_from); + purple_debug_info("yahoo", "Message from federated (%d) buddy %s.\n", im->fed, im->fed_from); } /* peer session id */ - if (pair->key == 11) { - if (im) - val_11 = strtol(pair->value, NULL, 10); + if (im && (pair->key == 11)) { + /* disconnect the peer if connected through p2p and sends wrong value for session id */ + if( (im->fed == YAHOO_FEDERATION_NONE) && (pkt_type == YAHOO_PKT_TYPE_P2P) + && (yd->session_id != strtol(pair->value, NULL, 10)) ) + { + purple_debug_warning("yahoo","p2p: %s sent us message with wrong session id. Disconnecting p2p connection to peer\n", im->fed_from); + /* remove from p2p connection lists, also calls yahoo_p2p_disconnect_destroy_data */ + g_hash_table_remove(yd->peers, im->fed_from); + g_free(im->fed_from); + g_free(im); + return; /* Not sure whether we should process remaining IMs in this packet */ + } } /* IMV key */ - if (pair->key == 63) + if (im && pair->key == 63) { - imv = pair->value; + /* Check for the Doodle IMV, no IMvironment for federated buddies */ + if (im->from != NULL && im->fed == YAHOO_FEDERATION_NONE) + { + g_hash_table_replace(yd->imvironments, g_strdup(im->from), g_strdup(pair->value)); + + if (strstr(pair->value, "doodle;") != NULL) + { + PurpleWhiteboard *wb; + + if (!purple_privacy_check(account, im->from)) { + purple_debug_info("yahoo", "Doodle request from %s dropped.\n", + im->from); + g_free(im->fed_from); + g_free(im); + return; + } + /* I'm not sure the following ever happens -DAA */ + wb = purple_whiteboard_get_session(account, im->from); + + /* If a Doodle session doesn't exist between this user */ + if(wb == NULL) + { + doodle_session *ds; + wb = purple_whiteboard_create(account, im->from, + DOODLE_STATE_REQUESTED); + ds = wb->proto_data; + ds->imv_key = g_strdup(pair->value); + + yahoo_doodle_command_send_request(gc, im->from, pair->value); + yahoo_doodle_command_send_ready(gc, im->from, pair->value); + } + } + } } if (pair->key == 429) if (im) @@ -1022,63 +1068,19 @@ _("Your Yahoo! message did not get sent."), NULL); } - /* disconnect the peer if connected through p2p and sends wrong value for session id */ - if( (pkt_type == YAHOO_PKT_TYPE_P2P) && (val_11 != yd->session_id) ) { - purple_debug_warning("yahoo","p2p: %s sent us message with wrong session id. Disconnecting p2p connection to peer\n", im ? fed_from : "(im was null)"); - /* remove from p2p connection lists, also calls yahoo_p2p_disconnect_destroy_data */ - if (im) { - g_hash_table_remove(yd->peers, fed_from); - g_free(im); - } - return; - } - - /* TODO: It seems that this check should be per IM, not global */ - /* Check for the Doodle IMV */ - /* no doodle with federated buddies -- assumption??? */ - if (im != NULL && imv!= NULL && im->from != NULL) - { - g_hash_table_replace(yd->imvironments, g_strdup(im->from), g_strdup(imv)); - - if (strstr(imv, "doodle;") != NULL) - { - PurpleWhiteboard *wb; - - if (!purple_privacy_check(account, im->from)) { - purple_debug_info("yahoo", "Doodle request from %s dropped.\n", im->from); - return; - } - - /* I'm not sure the following ever happens -DAA */ - - wb = purple_whiteboard_get_session(account, im->from); - - /* If a Doodle session doesn't exist between this user */ - if(wb == NULL) - { - doodle_session *ds; - wb = purple_whiteboard_create(account, im->from, DOODLE_STATE_REQUESTED); - ds = wb->proto_data; - ds->imv_key = g_strdup(imv); - - yahoo_doodle_command_send_request(gc, im->from, imv); - yahoo_doodle_command_send_ready(gc, im->from, imv); - } - } - } - for (l = list; l; l = l->next) { YahooFriend *f; char *m, *m2; im = l->data; - if (!fed_from || !im->msg) { + if (!im->fed_from || !im->msg) { + g_free(im->fed_from); g_free(im); continue; } - if (!purple_privacy_check(account, fed_from)) { - purple_debug_info("yahoo", "Message from %s dropped.\n", fed_from); + if (!purple_privacy_check(account, im->fed_from)) { + purple_debug_info("yahoo", "Message from %s dropped.\n", im->fed_from); return; } @@ -1116,10 +1118,11 @@ if (!strcmp(m, "")) { char *username; - username = g_markup_escape_text(fed_from, -1); + username = g_markup_escape_text(im->fed_from, -1); purple_prpl_got_attention(gc, username, YAHOO_BUZZ); g_free(username); g_free(m); + g_free(im->fed_from); g_free(im); continue; } @@ -1127,7 +1130,7 @@ m2 = yahoo_codes_to_html(m); g_free(m); - serv_got_im(gc, fed_from, m2, 0, im->time); + serv_got_im(gc, im->fed_from, m2, 0, im->time); g_free(m2); /* Official clients don't share buddy images with federated buddies */ @@ -1140,10 +1143,10 @@ } } + g_free(im->fed_from); g_free(im); } - if (fed_from != im->from) - g_free(fed_from); + g_slist_free(list); } @@ -3996,7 +3999,7 @@ } - if (f && f->status != YAHOO_STATUS_OFFLINE) { + if (f && f->status != YAHOO_STATUS_OFFLINE && f->fed == YAHOO_FEDERATION_NONE) { if (!yd->wm) { act = purple_menu_action_new(_("Join in Chat"), PURPLE_CALLBACK(yahoo_chat_goto_menu), @@ -4036,10 +4039,12 @@ build_presence_submenu(f, gc)); m = g_list_append(m, act); - act = purple_menu_action_new(_("Start Doodling"), - PURPLE_CALLBACK(yahoo_doodle_blist_node), - NULL, NULL); - m = g_list_append(m, act); + if (f->fed == YAHOO_FEDERATION_NONE) { + act = purple_menu_action_new(_("Start Doodling"), + PURPLE_CALLBACK(yahoo_doodle_blist_node), + NULL, NULL); + m = g_list_append(m, act); + } act = purple_menu_action_new(_("Set User Info..."), PURPLE_CALLBACK(yahoo_userinfo_blist_node), @@ -4364,17 +4369,7 @@ } } - if (who[3] == '/') { - if (!g_ascii_strncasecmp(who, "msn/", 4)) { - fed = YAHOO_FEDERATION_MSN; - } - else if (!g_ascii_strncasecmp(who, "ocs/", 4)) { - fed = YAHOO_FEDERATION_OCS; - } - else if (!g_ascii_strncasecmp(who, "ibm/", 4)) { - fed = YAHOO_FEDERATION_IBM; - } - } + fed = yahoo_get_federation_from_name(who); if (who[0] == '+') { /* we have an sms to be sent */ @@ -4506,17 +4501,7 @@ YahooFederation fed = YAHOO_FEDERATION_NONE; struct yahoo_packet *pkt = NULL; - if (who[3] == '/') { - if (!g_ascii_strncasecmp(who, "msn/", 4)) { - fed = YAHOO_FEDERATION_MSN; - } - else if (!g_ascii_strncasecmp(who, "ocs/", 4)) { - fed = YAHOO_FEDERATION_OCS; - } - else if (!g_ascii_strncasecmp(who, "ibm/", 4)) { - fed = YAHOO_FEDERATION_IBM; - } - } + fed = yahoo_get_federation_from_name(who); /* Don't do anything if sms is being typed */ if( strncmp(who, "+", 1) == 0 ) @@ -4808,18 +4793,9 @@ return; f = yahoo_friend_find(gc, bname); - if (bname[3] == '/') { + fed = yahoo_get_federation_from_name(bname); + if (fed != YAHOO_FEDERATION_NONE) fed_bname += 4; - if (!g_ascii_strncasecmp(bname, "msn/", 4)) { - fed = YAHOO_FEDERATION_MSN; - } - else if (!g_ascii_strncasecmp(bname, "ocs/", 4)) { - fed = YAHOO_FEDERATION_OCS; - } - else if (!g_ascii_strncasecmp(bname, "ibm/", 4)) { - fed = YAHOO_FEDERATION_IBM; - } - } g = purple_buddy_get_group(buddy); if (g) @@ -4931,18 +4907,8 @@ if (!who || who[0] == '\0') return; - if (who[3] == '/') { - if (!g_ascii_strncasecmp(who, "msn/", 4)) { - fed = YAHOO_FEDERATION_MSN; - } - else if (!g_ascii_strncasecmp(who, "ocs/", 4)) { - fed = YAHOO_FEDERATION_OCS; - } - else if (!g_ascii_strncasecmp(who, "ibm/", 4)) { - fed = YAHOO_FEDERATION_IBM; - } - } - + fed = yahoo_get_federation_from_name(who); + pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, yd->session_id); if(fed) @@ -4963,17 +4929,8 @@ if (!who || who[0] == '\0') return; - if (who[3] == '/') { - if (!g_ascii_strncasecmp(who, "msn/", 4)) { - fed = YAHOO_FEDERATION_MSN; - } - else if (!g_ascii_strncasecmp(who, "ocs/", 4)) { - fed = YAHOO_FEDERATION_OCS; - } - else if (!g_ascii_strncasecmp(who, "ibm/", 4)) { - fed = YAHOO_FEDERATION_IBM; - } - } + fed = yahoo_get_federation_from_name(who); + pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, yd->session_id); if(fed) diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/yahoo/libymsg.h --- a/libpurple/protocols/yahoo/libymsg.h Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/yahoo/libymsg.h Mon Nov 09 01:42:24 2009 +0000 @@ -346,6 +346,7 @@ char *yahoo_convert_to_numeric(const char *str); +YahooFederation yahoo_get_federation_from_name(const char *who); /* yahoo_profile.c */ void yahoo_get_info(PurpleConnection *gc, const char *name); diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/yahoo/util.c --- a/libpurple/protocols/yahoo/util.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/yahoo/util.c Mon Nov 09 01:42:24 2009 +0000 @@ -916,3 +916,18 @@ return g_string_free(dest, FALSE); } + +YahooFederation yahoo_get_federation_from_name(const char *who) +{ + YahooFederation fed = YAHOO_FEDERATION_NONE; + if (who[3] == '/') { + if (!g_ascii_strncasecmp(who, "msn", 3)) + fed = YAHOO_FEDERATION_MSN; + else if (!g_ascii_strncasecmp(who, "ocs", 3)) + fed = YAHOO_FEDERATION_OCS; + else if (!g_ascii_strncasecmp(who, "ibm", 3)) + fed = YAHOO_FEDERATION_IBM; + } + return fed; +} + diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/yahoo/yahoo_filexfer.c --- a/libpurple/protocols/yahoo/yahoo_filexfer.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/yahoo/yahoo_filexfer.c Mon Nov 09 01:42:24 2009 +0000 @@ -1070,6 +1070,13 @@ yahoo_packet_send_and_free(pkt, yd); } +gboolean yahoo_can_receive_file(PurpleConnection *gc, const char *who) +{ + if (!who || yahoo_get_federation_from_name(who) != YAHOO_FEDERATION_NONE) + return FALSE; + return TRUE; +} + void yahoo_send_file(PurpleConnection *gc, const char *who, const char *file) { struct yahoo_xfer_data *xfer_data; diff -r 994e8d214754 -r 585d6f844f79 libpurple/protocols/yahoo/yahoo_filexfer.h --- a/libpurple/protocols/yahoo/yahoo_filexfer.h Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/protocols/yahoo/yahoo_filexfer.h Mon Nov 09 01:42:24 2009 +0000 @@ -43,6 +43,18 @@ PurpleXfer *yahoo_new_xfer(PurpleConnection *gc, const char *who); /** + * Returns TRUE if the buddy can receive file, FALSE otherwise. + * Federated users cannot receive files. So this will return FALSE only + * for them. + * + * @param gc The connection + * @param who The name of the remote user + * + * @return TRUE or FALSE + */ +gboolean yahoo_can_receive_file(PurpleConnection *gc, const char *who); + +/** * Send a file. * * @param gc The PurpleConnection handle. diff -r 994e8d214754 -r 585d6f844f79 libpurple/server.c --- a/libpurple/server.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/server.c Mon Nov 09 01:42:24 2009 +0000 @@ -786,14 +786,14 @@ struct chat_invite_data *cid; int plugin_return; + g_return_if_fail(name != NULL); + g_return_if_fail(who != NULL); + account = purple_connection_get_account(gc); - if (PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc))->set_permit_deny == NULL) { - /* protocol does not support privacy, handle it ourselves */ - if (!purple_privacy_check(account, who)) { - purple_signal_emit(purple_conversations_get_handle(), "chat-invite-blocked", - account, who, name, message, data); - return; - } + if (!purple_privacy_check(account, who)) { + purple_signal_emit(purple_conversations_get_handle(), "chat-invite-blocked", + account, who, name, message, data); + return; } cid = g_new0(struct chat_invite_data, 1); diff -r 994e8d214754 -r 585d6f844f79 libpurple/tests/test_jabber_jutil.c --- a/libpurple/tests/test_jabber_jutil.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/tests/test_jabber_jutil.c Mon Nov 09 01:42:24 2009 +0000 @@ -134,6 +134,14 @@ assert_invalid_jid("paul@2[::1]124/as"); assert_invalid_jid("paul@まつ.おおかみ/\x01"); + /* + * RFC 3454 Section 6 reads, in part, + * "If a string contains any RandALCat character, the + * string MUST NOT contain any LCat character." + * The character is U+066D (ARABIC FIVE POINTED STAR). + */ + assert_invalid_jid("foo@example.com/٭simplexe٭"); + /* Ensure that jabber_id_new is properly lowercasing node and domains */ assert_jid_parts("paul", "darkrain42.org", "PaUL@darkrain42.org"); assert_jid_parts("paul", "darkrain42.org", "paul@DaRkRaIn42.org"); diff -r 994e8d214754 -r 585d6f844f79 libpurple/theme-loader.c --- a/libpurple/theme-loader.c Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/theme-loader.c Mon Nov 09 01:42:24 2009 +0000 @@ -100,6 +100,7 @@ PurpleThemeLoaderPrivate *priv = PURPLE_THEME_LOADER_GET_PRIVATE(loader); g_free(priv->type); + g_free(priv); parent_class->finalize(obj); } diff -r 994e8d214754 -r 585d6f844f79 libpurple/win32/global.mak --- a/libpurple/win32/global.mak Sun Nov 08 01:12:44 2009 +0000 +++ b/libpurple/win32/global.mak Mon Nov 09 01:42:24 2009 +0000 @@ -85,7 +85,7 @@ DEFINES += -DHAVE_CYRUS_SASL endif -DEFINES += -DHAVE_CONFIG_H +DEFINES += -DHAVE_CONFIG_H -DWIN32_LEAN_AND_MEAN # Use -g flag when building debug version of Pidgin (including plugins). # Use -fnative-struct instead of -mms-bitfields when using mingw 1.1 diff -r 994e8d214754 -r 585d6f844f79 pidgin.desktop.in --- a/pidgin.desktop.in Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin.desktop.in Mon Nov 09 01:42:24 2009 +0000 @@ -1,7 +1,7 @@ [Desktop Entry] _Name=Pidgin Internet Messenger _GenericName=Internet Messenger -_Comment=Send instant messages over multiple protocols +_Comment=Chat over IM. Supports AIM, Google Talk, Jabber/XMPP, MSN, Yahoo and more Exec=pidgin Icon=pidgin StartupNotify=true diff -r 994e8d214754 -r 585d6f844f79 pidgin/Makefile.mingw --- a/pidgin/Makefile.mingw Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/Makefile.mingw Mon Nov 09 01:42:24 2009 +0000 @@ -7,6 +7,8 @@ PIDGIN_TREE_TOP := .. include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak +DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES)) + NEEDED_DLLS = $(GTKSPELL_TOP)/gtkspell/libgtkspell.dll ## @@ -55,12 +57,12 @@ ## PIDGIN_C_SRC = \ gtkaccount.c \ - gtkblist.c \ + gtkblist-theme-loader.c \ gtkblist-theme.c \ - gtkblist-theme-loader.c \ - gtkcertmgr.c \ + gtkblist.c \ gtkcellrendererexpander.c \ gtkcellrendererprogress.c \ + gtkcertmgr.c \ gtkconn.c \ gtkconv.c \ gtkdebug.c \ @@ -70,8 +72,8 @@ gtkeventloop.c \ gtkexpander.c \ gtkft.c \ + gtkicon-theme-loader.c \ gtkicon-theme.c \ - gtkicon-theme-loader.c \ gtkidle.c \ gtkimhtml.c \ gtkimhtmltoolbar.c \ diff -r 994e8d214754 -r 585d6f844f79 pidgin/gtkblist.c --- a/pidgin/gtkblist.c Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/gtkblist.c Mon Nov 09 01:42:24 2009 +0000 @@ -4184,6 +4184,12 @@ } } + if (hidden_conv) { + char *tmp = nametext; + nametext = g_strdup_printf("%s", tmp); + g_free(tmp); + } + /* Put it all together */ if ((!aliased || biglist) && (statustext || idletime)) { /* using breaks the status, so it must be seperated into */ diff -r 994e8d214754 -r 585d6f844f79 pidgin/gtkconn.c --- a/pidgin/gtkconn.c Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/gtkconn.c Mon Nov 09 01:42:24 2009 +0000 @@ -142,7 +142,6 @@ { PurpleAccount *account = NULL; PidginAutoRecon *info; - GList *list; account = purple_connection_get_account(gc); info = g_hash_table_lookup(auto_reconns, account); @@ -164,17 +163,6 @@ purple_account_set_enabled(account, PIDGIN_UI, FALSE); } - - /* If we have any open chats, we probably want to rejoin when we get back online. */ - list = purple_get_chats(); - while (list) { - PurpleConversation *conv = list->data; - list = list->next; - if (conv->account != account || - purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) - continue; - purple_conversation_set_data(conv, "want-to-rejoin", GINT_TO_POINTER(TRUE)); - } } static void pidgin_connection_network_connected (void) diff -r 994e8d214754 -r 585d6f844f79 pidgin/gtkconv.c --- a/pidgin/gtkconv.c Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/gtkconv.c Mon Nov 09 01:42:24 2009 +0000 @@ -4185,7 +4185,7 @@ /* Users */ for (; l != NULL; l = l->next) { tab_complete_process_item(&most_matched, entered, entered_bytes, &partial, nick_partial, - &matches, TRUE, ((PurpleConvChatBuddy *)l->data)->name); + &matches, FALSE, ((PurpleConvChatBuddy *)l->data)->name); } @@ -7234,6 +7234,28 @@ } } +static void +account_signing_off(PurpleConnection *gc) +{ + GList *list = purple_get_chats(); + PurpleAccount *account = purple_connection_get_account(gc); + + /* We are about to sign off. See which chats we are currently in, and mark + * them for rejoin on reconnect. */ + while (list) { + PurpleConversation *conv = list->data; + if (!purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)) && + purple_conversation_get_account(conv) == account) { + purple_conversation_set_data(conv, "want-to-rejoin", GINT_TO_POINTER(TRUE)); + purple_conversation_write(conv, NULL, _("The account has disconnected and you are no " + "longer in this chat. You will be automatically rejoined in the chat when " + "the account reconnects."), + PURPLE_MESSAGE_SYSTEM, time(NULL)); + } + list = list->next; + } +} + static gboolean update_buddy_status_timeout(PurpleBuddy *buddy) { @@ -7728,6 +7750,8 @@ purple_signal_connect(purple_connections_get_handle(), "signed-off", handle, G_CALLBACK(account_signed_off_cb), GINT_TO_POINTER(PURPLE_CONV_ACCOUNT_OFFLINE)); + purple_signal_connect(purple_connections_get_handle(), "signing-off", handle, + G_CALLBACK(account_signing_off), NULL); purple_signal_connect(purple_conversations_get_handle(), "received-im-msg", handle, G_CALLBACK(received_im_msg_cb), NULL); diff -r 994e8d214754 -r 585d6f844f79 pidgin/gtkimhtml.c --- a/pidgin/gtkimhtml.c Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/gtkimhtml.c Mon Nov 09 01:42:24 2009 +0000 @@ -5053,7 +5053,7 @@ It will be destroyed when 'anchor' is destroyed. */ anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter); g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", text, g_free); - g_object_set_data(G_OBJECT(anchor), "gtkimhtml_tiptext", text); + g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_tiptext", g_strdup(text), g_free); g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley), g_free); /* This catches the expose events generated by animated @@ -5075,7 +5075,8 @@ gtk_container_add(GTK_CONTAINER(ebox), img); gtk_widget_show(img); g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", text, g_free); - g_object_set_data(G_OBJECT(anchor), "gtkimhtml_tiptext", text); + g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_tiptext", + g_strdup(text), g_free); g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley), g_free); gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), ebox, anchor); } diff -r 994e8d214754 -r 585d6f844f79 pidgin/gtkmain.c --- a/pidgin/gtkmain.c Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/gtkmain.c Mon Nov 09 01:42:24 2009 +0000 @@ -143,6 +143,10 @@ } #ifdef HAVE_SIGNAL_H +static char *segfault_message; + +static int signal_sockets[2]; + static void sighandler(int sig); /* @@ -168,31 +172,60 @@ } } -char *segfault_message; +static void sighandler(int sig) +{ + ssize_t written; + + /* + * We won't do any of the heavy lifting for the signal handling here + * because we have no idea what was interrupted. Previously this signal + * handler could result in some calls to malloc/free, which can cause + * deadlock in libc when the signal handler was interrupting a previous + * malloc or free. So instead we'll do an ugly hack where we write the + * signal number to one end of a socket pair. The other half of the + * socket pair is watched by our main loop. When the main loop sees new + * data on the socket it reads in the signal and performs the appropriate + * action without fear of interrupting stuff. + */ + if (sig == SIGSEGV) { + fprintf(stderr, "%s", segfault_message); + abort(); + return; + } -/* - * This signal handler shouldn't be touching this much stuff. - * It should just set a flag and return, and something else in - * Pidgin should monitor the flag to see if something needs to - * be done. Because the signal handler interrupts the program, - * it could be called in the middle of adding a new connection - * to the list of connections, and then if we try to disconnect - * all connections it could lead to a crash because the linked - * list of connections could be in a weird state. But, well, - * this signal handler probably isn't called very often, so it's - * not a big deal. - */ -static void -sighandler(int sig) + written = write(signal_sockets[0], &sig, sizeof(int)); + if (written < 0 || written != sizeof(int)) { + /* This should never happen */ + purple_debug_error("sighandler", "Received signal %d but only " + "wrote %" G_GSSIZE_FORMAT " bytes out of %" + G_GSIZE_FORMAT ": %s\n", + sig, written, sizeof(int), g_strerror(errno)); + exit(1); + } +} + +static gboolean +mainloop_sighandler(GIOChannel *source, GIOCondition cond, gpointer data) { + GIOStatus stat; + int sig; + gsize bytes_read; + GError *error = NULL; + + /* read the signal number off of the io channel */ + stat = g_io_channel_read_chars(source, (gchar *)&sig, sizeof(int), + &bytes_read, &error); + if (stat != G_IO_STATUS_NORMAL) { + purple_debug_error("sighandler", "Signal callback failed to read " + "from signal socket: %s", error->message); + purple_core_quit(); + return FALSE; + } + switch (sig) { case SIGHUP: purple_debug_warning("sighandler", "Caught signal %d\n", sig); break; - case SIGSEGV: - fprintf(stderr, "%s", segfault_message); - abort(); - break; #if defined(USE_GSTREAMER) && !defined(GST_CAN_DISABLE_FORKING) /* By default, gstreamer forks when you initialize it, and waitpids for the * child. But if libpurple reaps the child rather than leaving it to @@ -219,6 +252,8 @@ purple_debug_warning("sighandler", "Caught signal %d\n", sig); purple_core_quit(); } + + return TRUE; } #endif @@ -502,11 +537,13 @@ sigset_t sigset; RETSIGTYPE (*prev_sig_disp)(int); char errmsg[BUFSIZ]; + GIOChannel *signal_channel; + GIOStatus signal_status; #ifndef DEBUG char *segfault_message_tmp; +#endif GError *error = NULL; #endif -#endif int opt; gboolean gui_check; gboolean debug_enabled; @@ -592,6 +629,29 @@ ); #endif + /* + * Create a socket pair for receiving unix signals from a signal + * handler. + */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sockets) < 0) { + perror("Failed to create sockets for GLib signal handling"); + exit(1); + } + signal_channel = g_io_channel_unix_new(signal_sockets[1]); + + /* + * Set the channel encoding to raw binary instead of the default of + * UTF-8, because we'll be sending integers across instead of strings. + */ + error = NULL; + signal_status = g_io_channel_set_encoding(signal_channel, NULL, &error); + if (signal_status != G_IO_STATUS_NORMAL) { + fprintf(stderr, "Failed to set the signal channel to raw " + "binary: %s", error->message); + exit(1); + } + g_io_add_watch(signal_channel, G_IO_IN, mainloop_sighandler, NULL); + /* Let's not violate any PLA's!!!! */ /* jseymour: whatever the fsck that means */ /* Robot101: for some reason things like gdm like to block * @@ -744,7 +804,7 @@ } #if GLIB_CHECK_VERSION(2,2,0) - g_set_application_name(_("Pidgin")); + g_set_application_name(PIDGIN_NAME); #endif /* glib-2.0 >= 2.2.0 */ #ifdef _WIN32 diff -r 994e8d214754 -r 585d6f844f79 pidgin/gtkmedia.c --- a/pidgin/gtkmedia.c Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/gtkmedia.c Mon Nov 09 01:42:24 2009 +0000 @@ -89,6 +89,7 @@ GtkWidget *menubar; GtkWidget *statusbar; + GtkWidget *hold; GtkWidget *mute; GtkWidget *pause; @@ -187,6 +188,15 @@ } static void +pidgin_media_hold_toggled(GtkToggleButton *toggle, PidginMedia *media) +{ + purple_media_stream_info(media->priv->media, + gtk_toggle_button_get_active(toggle) ? + PURPLE_MEDIA_INFO_HOLD : PURPLE_MEDIA_INFO_UNHOLD, + NULL, NULL, TRUE); +} + +static void pidgin_media_mute_toggled(GtkToggleButton *toggle, PidginMedia *media) { purple_media_stream_info(media->priv->media, @@ -633,6 +643,16 @@ FALSE, FALSE, 0); gtk_widget_show(GTK_WIDGET(button_widget)); gtk_widget_show(send_widget); + + /* Hold button */ + gtkmedia->priv->hold = + gtk_toggle_button_new_with_mnemonic("_Hold"); + g_signal_connect(gtkmedia->priv->hold, "toggled", + G_CALLBACK(pidgin_media_hold_toggled), + gtkmedia); + gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->hold, + FALSE, FALSE, 0); + gtk_widget_show(gtkmedia->priv->hold); } else { send_widget = gtkmedia->priv->send_widget; button_widget = gtkmedia->priv->button_widget; diff -r 994e8d214754 -r 585d6f844f79 pidgin/gtkpounce.c --- a/pidgin/gtkpounce.c Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/gtkpounce.c Mon Nov 09 01:42:24 2009 +0000 @@ -1455,7 +1455,7 @@ * Here we place the protocol name in the pounce dialog to lessen * confusion about what protocol a pounce is for. */ - tmp = g_strdup_printf( + tmp = g_strdup( (events & PURPLE_POUNCE_TYPING) ? _("Started typing") : (events & PURPLE_POUNCE_TYPED) ? diff -r 994e8d214754 -r 585d6f844f79 pidgin/gtkprefs.c --- a/pidgin/gtkprefs.c Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/gtkprefs.c Mon Nov 09 01:42:24 2009 +0000 @@ -77,6 +77,7 @@ static GtkListStore *smiley_theme_store = NULL; static GtkTreeSelection *smiley_theme_sel = NULL; static GtkWidget *prefs_proxy_frame = NULL; +static GtkWidget *prefs_proxy_subframe = NULL; static GtkWidget *prefs = NULL; static GtkWidget *debugbutton = NULL; @@ -624,7 +625,8 @@ _("The default Pidgin status icon theme")); gtk_list_store_set(prefs_status_icon_themes, &iter, 0, pixbuf, 1, tmp, 2, "", -1); g_free(tmp); - g_object_unref(G_OBJECT(pixbuf)); + if (pixbuf) + g_object_unref(G_OBJECT(pixbuf)); purple_theme_manager_for_each_theme(prefs_themes_sort); pref_sound_generate_markup(); @@ -1132,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); @@ -1886,23 +1888,27 @@ pidgin_prefs_checkbox(_("_Enable automatic router port forwarding"), "/purple/network/map_ports", vbox); - ports_checkbox = pidgin_prefs_checkbox(_("_Manually specify range of ports to listen on"), - "/purple/network/ports_range_use", vbox); - - spin_button = pidgin_prefs_labeled_spin_button(vbox, _("_Start port:"), + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + + ports_checkbox = pidgin_prefs_checkbox(_("_Manually specify range of ports to listen on:"), + "/purple/network/ports_range_use", hbox); + + spin_button = pidgin_prefs_labeled_spin_button(hbox, _("_Start:"), "/purple/network/ports_range_start", 0, 65535, sg); if (!purple_prefs_get_bool("/purple/network/ports_range_use")) gtk_widget_set_sensitive(GTK_WIDGET(spin_button), FALSE); g_signal_connect(G_OBJECT(ports_checkbox), "clicked", G_CALLBACK(pidgin_toggle_sensitive), spin_button); - spin_button = pidgin_prefs_labeled_spin_button(vbox, _("_End port:"), + spin_button = pidgin_prefs_labeled_spin_button(hbox, _("_End:"), "/purple/network/ports_range_end", 0, 65535, sg); if (!purple_prefs_get_bool("/purple/network/ports_range_use")) gtk_widget_set_sensitive(GTK_WIDGET(spin_button), FALSE); g_signal_connect(G_OBJECT(ports_checkbox), "clicked", G_CALLBACK(pidgin_toggle_sensitive), spin_button); + pidgin_add_widget_to_vbox(GTK_BOX(vbox), NULL, NULL, hbox, TRUE, NULL); + g_object_unref(sg); /* TURN server */ @@ -1921,9 +1927,9 @@ pidgin_prefs_labeled_spin_button(hbox, _("_Port:"), "/purple/network/turn_port", 0, 65535, NULL); - hbox = pidgin_prefs_labeled_entry(vbox, _("_Username:"), + hbox = pidgin_prefs_labeled_entry(vbox, _("Use_rname:"), "/purple/network/turn_username", sg); - pidgin_prefs_labeled_password(hbox, _("_Password:"), + pidgin_prefs_labeled_password(hbox, _("Pass_word:"), "/purple/network/turn_password", NULL); if (purple_running_gnome()) { @@ -1967,9 +1973,15 @@ gtk_widget_show(browser_button); } else { vbox = pidgin_make_frame(ret, _("Proxy Server")); - prefs_proxy_frame = gtk_vbox_new(FALSE, 0); - - pidgin_prefs_dropdown(vbox, _("Proxy _type:"), PURPLE_PREF_STRING, + prefs_proxy_frame = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + prefs_proxy_subframe = gtk_vbox_new(FALSE, 0); + + /* This is a global option that affects SOCKS4 usage even with account-specific proxy settings */ + pidgin_prefs_checkbox(_("Use remote _DNS with SOCKS4 proxies"), + "/purple/proxy/socks4_remotedns", prefs_proxy_frame); + gtk_box_pack_start(GTK_BOX(vbox), prefs_proxy_frame, 0, 0, 0); + + pidgin_prefs_dropdown(prefs_proxy_frame, _("Proxy t_ype:"), PURPLE_PREF_STRING, "/purple/proxy/type", _("No proxy"), "none", "SOCKS 4", "socks4", @@ -1977,21 +1989,17 @@ "HTTP", "http", _("Use Environmental Settings"), "envvar", NULL); - gtk_box_pack_start(GTK_BOX(vbox), prefs_proxy_frame, 0, 0, 0); + gtk_box_pack_start(GTK_BOX(prefs_proxy_frame), prefs_proxy_subframe, 0, 0, 0); proxy_info = purple_global_proxy_get_info(); purple_prefs_connect_callback(prefs, "/purple/proxy/type", - proxy_changed_cb, prefs_proxy_frame); - - /* This is a global option that affects SOCKS4 usage even with account-specific proxy settings */ - pidgin_prefs_checkbox(_("Use remote DNS with SOCKS4 proxies"), - "/purple/proxy/socks4_remotedns", prefs_proxy_frame); + proxy_changed_cb, prefs_proxy_subframe); table = gtk_table_new(4, 2, FALSE); gtk_container_set_border_width(GTK_CONTAINER(table), 0); gtk_table_set_col_spacings(GTK_TABLE(table), 5); gtk_table_set_row_spacings(GTK_TABLE(table), 10); - gtk_container_add(GTK_CONTAINER(prefs_proxy_frame), table); + gtk_container_add(GTK_CONTAINER(prefs_proxy_subframe), table); label = gtk_label_new_with_mnemonic(_("_Host:")); @@ -2012,11 +2020,11 @@ gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); pidgin_set_accessible_label (entry, label); - label = gtk_label_new_with_mnemonic(_("_Port:")); + label = gtk_label_new_with_mnemonic(_("P_ort:")); gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5); gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); - entry = gtk_entry_new(); + entry = gtk_spin_button_new_with_range(0, 65535, 1); gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry); gtk_table_attach(GTK_TABLE(table), entry, 3, 4, 0, 1, GTK_FILL, 0, 0, 0); g_signal_connect(G_OBJECT(entry), "changed", @@ -2031,7 +2039,7 @@ } pidgin_set_accessible_label (entry, label); - label = gtk_label_new_with_mnemonic(_("_User:")); + label = gtk_label_new_with_mnemonic(_("User_name:")); gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); diff -r 994e8d214754 -r 585d6f844f79 pidgin/gtkrequest.c --- a/pidgin/gtkrequest.c Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/gtkrequest.c Mon Nov 09 01:42:24 2009 +0000 @@ -81,6 +81,33 @@ } PidginRequestData; static void +pidgin_widget_decorate_account(GtkWidget *cont, PurpleAccount *account) +{ + GtkWidget *image; + GdkPixbuf *pixbuf; + GtkTooltips *tips; + + if (!account) + return; + + pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL); + image = gtk_image_new_from_pixbuf(pixbuf); + g_object_unref(G_OBJECT(pixbuf)); + + tips = gtk_tooltips_new(); + gtk_tooltips_set_tip(tips, image, purple_account_get_username(account), NULL); + + if (GTK_IS_DIALOG(cont)) { + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(cont)->action_area), image, FALSE, TRUE, 0); + gtk_box_reorder_child(GTK_BOX(GTK_DIALOG(cont)->action_area), image, 0); + } else if (GTK_IS_HBOX(cont)) { + gtk_misc_set_alignment(GTK_MISC(image), 0, 0); + gtk_box_pack_end(GTK_BOX(cont), image, FALSE, TRUE, 0); + } + gtk_widget_show(image); +} + +static void generic_response_start(PidginRequestData *data) { g_return_if_fail(data != NULL); @@ -347,6 +374,8 @@ gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0); + pidgin_widget_decorate_account(hbox, account); + /* Descriptive label */ primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL; secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL; @@ -515,6 +544,8 @@ gtk_misc_set_alignment(GTK_MISC(img), 0, 0); gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0); + pidgin_widget_decorate_account(hbox, account); + /* Vertical box */ vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0); @@ -639,6 +670,8 @@ vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0); + pidgin_widget_decorate_account(hbox, account); + /* Descriptive label */ primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL; secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL; @@ -1144,6 +1177,8 @@ GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); gtk_window_set_default(GTK_WINDOW(win), button); + pidgin_widget_decorate_account(hbox, account); + /* Setup the vbox */ vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0); diff -r 994e8d214754 -r 585d6f844f79 pidgin/gtkroomlist.c --- a/pidgin/gtkroomlist.c Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/gtkroomlist.c Mon Nov 09 01:42:24 2009 +0000 @@ -111,7 +111,18 @@ static void dialog_select_account_cb(GObject *w, PurpleAccount *account, PidginRoomlistDialog *dialog) { + gboolean change = (account != dialog->account); dialog->account = account; + + if (change && dialog->roomlist) { + PidginRoomlist *rl = dialog->roomlist->ui_data; + if (rl->tree) { + gtk_widget_destroy(rl->tree); + rl->tree = NULL; + } + purple_roomlist_unref(dialog->roomlist); + dialog->roomlist = NULL; + } } static void list_button_cb(GtkButton *button, PidginRoomlistDialog *dialog) diff -r 994e8d214754 -r 585d6f844f79 pidgin/gtkstatusbox.c --- a/pidgin/gtkstatusbox.c Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/gtkstatusbox.c Mon Nov 09 01:42:24 2009 +0000 @@ -79,8 +79,8 @@ static void pidgin_status_box_pulse_typing(PidginStatusBox *status_box); static void pidgin_status_box_refresh(PidginStatusBox *status_box); -static void status_menu_refresh_iter(PidginStatusBox *status_box); -static void pidgin_status_box_regenerate(PidginStatusBox *status_box); +static void status_menu_refresh_iter(PidginStatusBox *status_box, gboolean status_changed); +static void pidgin_status_box_regenerate(PidginStatusBox *status_box, gboolean status_changed); static void pidgin_status_box_changed(PidginStatusBox *box); static void pidgin_status_box_size_request (GtkWidget *widget, GtkRequisition *requisition); static void pidgin_status_box_size_allocate (GtkWidget *widget, GtkAllocation *allocation); @@ -304,7 +304,7 @@ if (status_box->account == account) update_to_reflect_account_status(status_box, account, newstatus); else if (status_box->token_status_account == account) - status_menu_refresh_iter(status_box); + status_menu_refresh_iter(status_box, TRUE); } static gboolean @@ -312,6 +312,7 @@ { if (event->button == 3) { GtkWidget *menu_item; + const char *path; if (box->icon_box_menu) gtk_widget_destroy(box->icon_box_menu); @@ -325,7 +326,8 @@ menu_item = pidgin_new_item_from_stock(box->icon_box_menu, _("Remove"), GTK_STOCK_REMOVE, G_CALLBACK(remove_buddy_icon_cb), box, 0, 0, NULL); - if (purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon") == NULL) + if (!(path = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon")) + || !*path) gtk_widget_set_sensitive(menu_item, FALSE); gtk_menu_popup(GTK_MENU(box->icon_box_menu), NULL, NULL, NULL, NULL, @@ -559,7 +561,7 @@ else statusbox->token_status_account = check_active_accounts_for_identical_statuses(); - pidgin_status_box_regenerate(statusbox); + pidgin_status_box_regenerate(statusbox, TRUE); break; default: @@ -821,7 +823,7 @@ * keyboard signals instead of the changed signal? */ static void -status_menu_refresh_iter(PidginStatusBox *status_box) +status_menu_refresh_iter(PidginStatusBox *status_box, gboolean status_changed) { PurpleSavedStatus *saved_status; PurpleStatusPrimitive primitive; @@ -912,18 +914,15 @@ } else status_box->active_row = NULL; - message = purple_savedstatus_get_message(saved_status); - if (!purple_savedstatus_is_transient(saved_status) || !message || !*message) - { - status_box->imhtml_visible = FALSE; - gtk_widget_hide_all(status_box->vbox); - } - else - { - status_box->imhtml_visible = TRUE; - gtk_widget_show_all(status_box->vbox); + if (status_changed) { + message = purple_savedstatus_get_message(saved_status); /* + * If we are going to hide the imhtml, don't retain the + * message because showing the old message later is + * confusing. If we are going to set the message to a pre-set, + * then we need to do this anyway + * * Suppress the "changed" signal because the status * was changed programmatically. */ @@ -931,12 +930,24 @@ gtk_imhtml_clear(GTK_IMHTML(status_box->imhtml)); gtk_imhtml_clear_formatting(GTK_IMHTML(status_box->imhtml)); - gtk_imhtml_append_text(GTK_IMHTML(status_box->imhtml), message, 0); + + if (!purple_savedstatus_is_transient(saved_status) || !message || !*message) + { + status_box->imhtml_visible = FALSE; + gtk_widget_hide_all(status_box->vbox); + } + else + { + status_box->imhtml_visible = TRUE; + gtk_widget_show_all(status_box->vbox); + + gtk_imhtml_append_text(GTK_IMHTML(status_box->imhtml), message, 0); + } + gtk_widget_set_sensitive(GTK_WIDGET(status_box->imhtml), TRUE); + update_size(status_box); } - update_size(status_box); - /* Stop suppressing the "changed" signal. */ gtk_widget_set_sensitive(GTK_WIDGET(status_box), TRUE); } @@ -996,50 +1007,50 @@ * statuses and a token account if they do */ static PurpleAccount* check_active_accounts_for_identical_statuses(void) { - PurpleAccount *acct = NULL, *acct2; - GList *tmp, *tmp2, *active_accts = purple_accounts_get_all_active(); - GList *s, *s1, *s2; - - for (tmp = active_accts; tmp; tmp = tmp->next) { - acct = tmp->data; - s = purple_account_get_status_types(acct); - for (tmp2 = tmp->next; tmp2; tmp2 = tmp2->next) { - acct2 = tmp2->data; - - /* Only actually look at the statuses if the accounts use the same prpl */ - if (strcmp(purple_account_get_protocol_id(acct), purple_account_get_protocol_id(acct2))) { - acct = NULL; - break; - } - - s2 = purple_account_get_status_types(acct2); - - s1 = s; - while (s1 && s2) { - PurpleStatusType *st1 = s1->data, *st2 = s2->data; - /* TODO: Are these enough to consider the statuses identical? */ - if (purple_status_type_get_primitive(st1) != purple_status_type_get_primitive(st2) - || strcmp(purple_status_type_get_id(st1), purple_status_type_get_id(st2)) - || strcmp(purple_status_type_get_name(st1), purple_status_type_get_name(st2))) { - acct = NULL; - break; - } - - s1 = s1->next; - s2 = s2->next; - } - - if (s1 != s2) {/* Will both be NULL if matched */ - acct = NULL; + GList *iter, *active_accts = purple_accounts_get_all_active(); + PurpleAccount *acct1 = NULL; + const char *prpl1 = NULL; + + if (active_accts) { + acct1 = active_accts->data; + prpl1 = purple_account_get_protocol_id(acct1); + } else { + /* there's no enabled account */ + return NULL; + } + + /* start at the second account */ + for (iter = active_accts->next; iter; iter = iter->next) { + PurpleAccount *acct2 = iter->data; + GList *s1, *s2; + + if (!g_str_equal(prpl1, purple_account_get_protocol_id(acct2))) { + acct1 = NULL; + break; + } + + for (s1 = purple_account_get_status_types(acct1), + s2 = purple_account_get_status_types(acct2); s1 && s2; + s1 = s1->next, s2 = s2->next) { + PurpleStatusType *st1 = s1->data, *st2 = s2->data; + /* TODO: Are these enough to consider the statuses identical? */ + if (purple_status_type_get_primitive(st1) != purple_status_type_get_primitive(st2) + || strcmp(purple_status_type_get_id(st1), purple_status_type_get_id(st2)) + || strcmp(purple_status_type_get_name(st1), purple_status_type_get_name(st2))) { + acct1 = NULL; break; } } - if (!acct) + + if (s1 != s2) {/* Will both be NULL if matched */ + acct1 = NULL; break; + } } + g_list_free(active_accts); - return acct; + return acct1; } static void @@ -1068,7 +1079,7 @@ } static void -pidgin_status_box_regenerate(PidginStatusBox *status_box) +pidgin_status_box_regenerate(PidginStatusBox *status_box, gboolean status_changed) { GtkIconSize icon_size; @@ -1104,7 +1115,7 @@ pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_CUSTOM, NULL, _("New status..."), NULL, NULL); pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_SAVED, NULL, _("Saved statuses..."), NULL, NULL); - status_menu_refresh_iter(status_box); + status_menu_refresh_iter(status_box, status_changed); pidgin_status_box_refresh(status_box); } else { @@ -1156,7 +1167,7 @@ update_to_reflect_account_status(status_box, status_box->account, purple_account_get_active_status(status_box->account)); else { - status_menu_refresh_iter(status_box); + status_menu_refresh_iter(status_box, TRUE); pidgin_status_box_refresh(status_box); } return TRUE; @@ -1229,7 +1240,7 @@ /* Regenerate the list if it has changed */ if (initial_token_acct != status_box->token_status_account) { - pidgin_status_box_regenerate(status_box); + pidgin_status_box_regenerate(status_box, TRUE); } } @@ -1238,13 +1249,14 @@ current_savedstatus_changed_cb(PurpleSavedStatus *now, PurpleSavedStatus *old, PidginStatusBox *status_box) { /* Make sure our current status is added to the list of popular statuses */ - pidgin_status_box_regenerate(status_box); + pidgin_status_box_regenerate(status_box, TRUE); } static void saved_status_updated_cb(PurpleSavedStatus *status, PidginStatusBox *status_box) { - pidgin_status_box_regenerate(status_box); + pidgin_status_box_regenerate(status_box, + purple_savedstatus_get_current() == status); } static void @@ -1919,7 +1931,7 @@ status_box->token_status_account = check_active_accounts_for_identical_statuses(); cache_pixbufs(status_box); - pidgin_status_box_regenerate(status_box); + pidgin_status_box_regenerate(status_box, TRUE); purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-changed", status_box, @@ -2324,18 +2336,6 @@ pidgin_status_box_refresh(status_box); } -static gboolean -message_changed(const char *one, const char *two) -{ - if (one == NULL && two == NULL) - return FALSE; - - if (one == NULL || two == NULL) - return TRUE; - - return (g_utf8_collate(one, two) != 0); -} - static void activate_currently_selected_status(PidginStatusBox *status_box) { @@ -2386,6 +2386,7 @@ if (status_box->account == NULL) { PurpleStatusType *acct_status_type = NULL; + const char *id = NULL; /* id of acct_status_type */ PurpleStatusPrimitive primitive = GPOINTER_TO_INT(data); /* Global */ /* Save the newly selected status to prefs.xml and status.xml */ @@ -2394,7 +2395,6 @@ if (status_box->token_status_account) { gint active; PurpleStatus *status; - const char *id = NULL; GtkTreePath *path = gtk_tree_row_reference_get_path(status_box->active_row); active = gtk_tree_path_get_indices(path)[0]; @@ -2402,37 +2402,35 @@ status = purple_account_get_active_status(status_box->token_status_account); - acct_status_type = find_status_type_by_index(status_box->token_status_account, active); + acct_status_type = find_status_type_by_index(status_box->token_status_account, active); id = purple_status_type_get_id(acct_status_type); - if (strncmp(id, purple_status_get_id(status), strlen(id)) == 0) + if (g_str_equal(id, purple_status_get_id(status)) && + purple_strequal(message, purple_status_get_attr_string(status, "message"))) { /* Selected status and previous status is the same */ - if (!message_changed(message, purple_status_get_attr_string(status, "message"))) - { - PurpleSavedStatus *ss = purple_savedstatus_get_current(); - /* Make sure that statusbox displays the correct thing. - * It can get messed up if the previous selection was a - * saved status that wasn't supported by this account */ - if ((purple_savedstatus_get_type(ss) == primitive) - && purple_savedstatus_is_transient(ss) - && purple_savedstatus_has_substatuses(ss)) - changed = FALSE; - } + PurpleSavedStatus *ss = purple_savedstatus_get_current(); + /* Make sure that statusbox displays the correct thing. + * It can get messed up if the previous selection was a + * saved status that wasn't supported by this account */ + if ((purple_savedstatus_get_type(ss) == primitive) + && purple_savedstatus_is_transient(ss) + && purple_savedstatus_has_substatuses(ss)) + changed = FALSE; } } else { saved_status = purple_savedstatus_get_current(); if (purple_savedstatus_get_type(saved_status) == primitive && - !purple_savedstatus_has_substatuses(saved_status)) + !purple_savedstatus_has_substatuses(saved_status) && + purple_strequal(purple_savedstatus_get_message(saved_status), message)) { - if (!message_changed(purple_savedstatus_get_message(saved_status), message)) - changed = FALSE; + changed = FALSE; } } if (changed) { - /* Manually find the appropriate transient acct */ + /* Manually find the appropriate transient status */ if (status_box->token_status_account) { GList *iter = purple_savedstatuses_get_all(); GList *tmp, *active_accts = purple_accounts_get_all_active(); @@ -2440,27 +2438,31 @@ for (; iter != NULL; iter = iter->next) { PurpleSavedStatus *ss = iter->data; const char *ss_msg = purple_savedstatus_get_message(ss); + /* find a known transient status that is the same as the + * new selected one */ if ((purple_savedstatus_get_type(ss) == primitive) && purple_savedstatus_is_transient(ss) && purple_savedstatus_has_substatuses(ss) && /* Must have substatuses */ - !message_changed(ss_msg, message)) + purple_strequal(ss_msg, message)) { gboolean found = FALSE; - /* The currently enabled accounts must have substatuses for all the active accts */ + /* this status must have substatuses for all the active accts */ for(tmp = active_accts; tmp != NULL; tmp = tmp->next) { PurpleAccount *acct = tmp->data; PurpleSavedStatusSub *sub = purple_savedstatus_get_substatus(ss, acct); if (sub) { const PurpleStatusType *sub_type = purple_savedstatus_substatus_get_type(sub); const char *subtype_status_id = purple_status_type_get_id(sub_type); - if (subtype_status_id && !strcmp(subtype_status_id, - purple_status_type_get_id(acct_status_type))) + if (purple_strequal(subtype_status_id, id)) { found = TRUE; + break; + } } } - if (!found) - continue; - saved_status = ss; - break; + + if (found) { + saved_status = ss; + break; + } } } @@ -2503,11 +2505,11 @@ status_type = find_status_type_by_index(status_box->account, active); id = purple_status_type_get_id(status_type); - if (strncmp(id, purple_status_get_id(status), strlen(id)) == 0) + if (g_str_equal(id, purple_status_get_id(status)) && + purple_strequal(message, purple_status_get_attr_string(status, "message"))) { /* Selected status and previous status is the same */ - if (!message_changed(message, purple_status_get_attr_string(status, "message"))) - changed = FALSE; + changed = FALSE; } if (changed) @@ -2597,7 +2599,7 @@ if (status_box->typing == 0) { /* Nothing has changed, so we don't need to do anything */ - status_menu_refresh_iter(status_box); + status_menu_refresh_iter(status_box, FALSE); return; } @@ -2655,14 +2657,14 @@ pidgin_status_editor_show(FALSE, purple_savedstatus_is_transient(saved_status) ? saved_status : NULL); - status_menu_refresh_iter(status_box); + status_menu_refresh_iter(status_box, FALSE); return; } if (type == PIDGIN_STATUS_BOX_TYPE_SAVED) { pidgin_status_window_show(); - status_menu_refresh_iter(status_box); + status_menu_refresh_iter(status_box, FALSE); return; } } diff -r 994e8d214754 -r 585d6f844f79 pidgin/gtkutils.c --- a/pidgin/gtkutils.c Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/gtkutils.c Mon Nov 09 01:42:24 2009 +0000 @@ -75,7 +75,7 @@ } AopMenu; static guint accels_save_timer = 0; -static GList *gnome_url_handlers = NULL; +static GSList *registered_url_handlers = NULL; static gboolean url_clicked_idle_cb(gpointer data) @@ -3890,7 +3890,7 @@ start += sizeof("/desktop/gnome/url-handlers/") - 1; protocol = g_strdup_printf("%s:", start); - gnome_url_handlers = g_list_prepend(gnome_url_handlers, protocol); + registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol); gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu); } start = c + 1; @@ -3898,9 +3898,45 @@ } g_free(tmp); - return (gnome_url_handlers != NULL); + return (registered_url_handlers != NULL); } +#ifdef _WIN32 +static void +winpidgin_register_win32_url_handlers(void) +{ + int idx = 0; + LONG ret = ERROR_SUCCESS; + + do { + DWORD nameSize = 256; + char start[256]; + /* I don't think we need to worry about non-ASCII protocol names */ + ret = RegEnumKeyExA(HKEY_CLASSES_ROOT, idx++, start, &nameSize, + NULL, NULL, NULL, NULL); + if (ret == ERROR_SUCCESS) { + HKEY reg_key = NULL; + ret = RegOpenKeyExA(HKEY_CLASSES_ROOT, start, 0, KEY_READ, ®_key); + if (ret == ERROR_SUCCESS) { + ret = RegQueryValueExA(reg_key, "URL Protocol", NULL, NULL, NULL, NULL); + if (ret == ERROR_SUCCESS) { + gchar *protocol = g_strdup_printf("%s:", start); + registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol); + /* We still pass everything to the "http" "open" handler for security reasons */ + gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu); + } + RegCloseKey(reg_key); + } + ret = ERROR_SUCCESS; + } + } while (ret == ERROR_SUCCESS); + + if (ret != ERROR_NO_MORE_ITEMS) + purple_debug_error("winpidgin", "Error iterating HKEY_CLASSES_ROOT subkeys: %ld\n", + ret); +} +#endif + void pidgin_utils_init(void) { gtk_imhtml_class_register_protocol("http://", url_clicked_cb, link_context_menu); @@ -3918,6 +3954,11 @@ /* If we're under GNOME, try registering the system URL handlers. */ if (purple_running_gnome()) register_gnome_url_handlers(); + +#ifdef _WIN32 + winpidgin_register_win32_url_handlers(); +#endif + } void pidgin_utils_uninit(void) @@ -3925,16 +3966,16 @@ gtk_imhtml_class_register_protocol("open://", NULL, NULL); /* If we have GNOME handlers registered, unregister them. */ - if (gnome_url_handlers) + if (registered_url_handlers) { - GList *l; - for (l = gnome_url_handlers ; l ; l = l->next) + GSList *l; + for (l = registered_url_handlers; l; l = l->next) { gtk_imhtml_class_register_protocol((char *)l->data, NULL, NULL); g_free(l->data); } - g_list_free(gnome_url_handlers); - gnome_url_handlers = NULL; + g_slist_free(registered_url_handlers); + registered_url_handlers = NULL; return; } diff -r 994e8d214754 -r 585d6f844f79 pidgin/pixmaps/Makefile.am --- a/pidgin/pixmaps/Makefile.am Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/pixmaps/Makefile.am Mon Nov 09 01:42:24 2009 +0000 @@ -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 994e8d214754 -r 585d6f844f79 pidgin/pixmaps/protocols/16/mxit.png Binary file pidgin/pixmaps/protocols/16/mxit.png has changed diff -r 994e8d214754 -r 585d6f844f79 pidgin/pixmaps/protocols/22/mxit.png Binary file pidgin/pixmaps/protocols/22/mxit.png has changed diff -r 994e8d214754 -r 585d6f844f79 pidgin/pixmaps/protocols/48/mxit.png Binary file pidgin/pixmaps/protocols/48/mxit.png has changed diff -r 994e8d214754 -r 585d6f844f79 pidgin/pixmaps/protocols/scalable/mxit.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/pixmaps/protocols/scalable/mxit.svg Mon Nov 09 01:42:24 2009 +0000 @@ -0,0 +1,24 @@ + + + + + + + diff -r 994e8d214754 -r 585d6f844f79 pidgin/plugins/disco/gtkdisco.c --- a/pidgin/plugins/disco/gtkdisco.c Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/plugins/disco/gtkdisco.c Mon Nov 09 01:42:24 2009 +0000 @@ -141,8 +141,18 @@ static void dialog_select_account_cb(GObject *w, PurpleAccount *account, PidginDiscoDialog *dialog) { + gboolean change = (account != dialog->account); dialog->account = account; gtk_widget_set_sensitive(dialog->browse_button, account != NULL); + + if (change && dialog->discolist) { + if (dialog->discolist->tree) { + gtk_widget_destroy(dialog->discolist->tree); + dialog->discolist->tree = NULL; + } + pidgin_disco_list_unref(dialog->discolist); + dialog->discolist = NULL; + } } static void register_button_cb(GtkWidget *unused, PidginDiscoDialog *dialog) @@ -152,12 +162,15 @@ static void discolist_cancel_cb(PidginDiscoList *pdl, const char *server) { + pdl->dialog->prompt_handle = NULL; + pidgin_disco_list_set_in_progress(pdl, FALSE); pidgin_disco_list_unref(pdl); } static void discolist_ok_cb(PidginDiscoList *pdl, const char *server) { + pdl->dialog->prompt_handle = NULL; gtk_widget_set_sensitive(pdl->dialog->browse_button, TRUE); if (!server || !*server) { @@ -226,7 +239,7 @@ /* Note to translators: The string "Enter an XMPP Server" is asking the user to type the name of an XMPP server which will then be queried */ - purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"), + dialog->prompt_handle = purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"), _("Select an XMPP server to query"), server, FALSE, FALSE, NULL, _("Find Services"), PURPLE_CALLBACK(discolist_ok_cb), @@ -380,6 +393,9 @@ PidginDiscoDialog *dialog = d; PidginDiscoList *list = dialog->discolist; + if (dialog->prompt_handle) + purple_request_close(PURPLE_REQUEST_INPUT, dialog->prompt_handle); + if (list) { list->dialog = NULL; diff -r 994e8d214754 -r 585d6f844f79 pidgin/plugins/disco/gtkdisco.h --- a/pidgin/plugins/disco/gtkdisco.h Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/plugins/disco/gtkdisco.h Mon Nov 09 01:42:24 2009 +0000 @@ -43,6 +43,8 @@ PurpleAccount *account; PidginDiscoList *discolist; + + gpointer *prompt_handle; }; struct _PidginDiscoList { diff -r 994e8d214754 -r 585d6f844f79 pidgin/plugins/perl/common/Makefile.mingw --- a/pidgin/plugins/perl/common/Makefile.mingw Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/plugins/perl/common/Makefile.mingw Mon Nov 09 01:42:24 2009 +0000 @@ -5,9 +5,12 @@ # PIDGIN_TREE_TOP := ../../../.. -GCCWARNINGS := -Wno-comment -Waggregate-return -Wcast-align -Wdeclaration-after-statement -Werror-implicit-function-declaration -Wextra -Wno-sign-compare -Wno-unused-parameter -Winit-self -Wmissing-declarations -Wmissing-prototypes -Wpointer-arith -Wundef -Wno-unused include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak +GCCWARNINGS += -Wno-comment -Wno-unused -Wno-nested-externs + +DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES)) + TARGET = Pidgin EXTUTILS ?= C:/perl/lib/ExtUtils diff -r 994e8d214754 -r 585d6f844f79 pidgin/plugins/win32/winprefs/Makefile.mingw --- a/pidgin/plugins/win32/winprefs/Makefile.mingw Sun Nov 08 01:12:44 2009 +0000 +++ b/pidgin/plugins/win32/winprefs/Makefile.mingw Mon Nov 09 01:42:24 2009 +0000 @@ -8,6 +8,7 @@ include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak TARGET = winprefs +DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES)) DEFINES += -DWINVER=0x500 ## diff -r 994e8d214754 -r 585d6f844f79 po/ChangeLog --- a/po/ChangeLog Sun Nov 08 01:12:44 2009 +0000 +++ b/po/ChangeLog Mon Nov 09 01:42:24 2009 +0000 @@ -1,7 +1,10 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.6.4 + * Vietnamese translation updated (Clytie Siddall) + version 2.6.3 - * Vietnamese translation updated (Clytie Siddall) + * No changes version 2.6.2 * Afrikaans translation updated (Friedel Wolff) diff -r 994e8d214754 -r 585d6f844f79 po/POTFILES.in --- a/po/POTFILES.in Sun Nov 08 01:12:44 2009 +0000 +++ b/po/POTFILES.in Mon Nov 09 01:42:24 2009 +0000 @@ -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 diff -r 994e8d214754 -r 585d6f844f79 po/ca.po --- a/po/ca.po Sun Nov 08 01:12:44 2009 +0000 +++ b/po/ca.po Mon Nov 09 01:42:24 2009 +0000 @@ -33,8 +33,8 @@ msgstr "" "Project-Id-Version: Pidgin\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-09-07 18:26-0700\n" -"PO-Revision-Date: 2009-07-26 17:23+0200\n" +"POT-Creation-Date: 2009-10-25 17:57+0100\n" +"PO-Revision-Date: 2009-10-26 09:20+0100\n" "Last-Translator: Josep Puigdemont i Casamajó \n" "Language-Team: Catalan \n" "MIME-Version: 1.0\n" @@ -576,13 +576,6 @@ msgid "Re-enable Account" msgstr "Rehabilita el compte" -msgid "" -"The account has disconnected and you are no longer in this chat. You will be " -"automatically rejoined in the chat when the account reconnects." -msgstr "" -"El compte s'ha desconnectat i ja no sou al xat. Quan es torni a connectar el " -"compte entrareu de nou automàticament al xat." - msgid "No such command." msgstr "No existeix l'ordre." @@ -625,6 +618,13 @@ msgid "You have left this chat." msgstr "Heu sortit d'aquest xat" +msgid "" +"The account has disconnected and you are no longer in this chat. You will be " +"automatically rejoined in the chat when the account reconnects." +msgstr "" +"El compte s'ha desconnectat i ja no sou al xat. Quan es torni a connectar el " +"compte entrareu de nou automàticament al xat." + msgid "Logging started. Future messages in this conversation will be logged." msgstr "" "S'ha iniciat el registre. Es registraran els propers missatges d'aquesta " @@ -661,6 +661,9 @@ msgid "Enable Sounds" msgstr "Habilita els sons" +msgid "You are not connected." +msgstr "No esteu connectat." + msgid " " msgstr " " @@ -670,8 +673,8 @@ msgstr[0] "Llista d'%d usuari:\n" msgstr[1] "Llista de %d usuaris:\n" -msgid "Supported debug options are: version" -msgstr "Les opcions de depuració disponibles són: version" +msgid "Supported debug options are: plugins version" +msgstr "Les opcions de depuració disponibles són: plugins version" msgid "No such command (in this context)." msgstr "L'ordre no existeix (en aquest context)." @@ -977,6 +980,9 @@ msgid "(none)" msgstr "(cap)" +#. XXX: The following expects that finch_notify_message gets called. This +#. * may not always happen, e.g. when another plugin sets its own +#. * notify_message. So tread carefully. msgid "URI" msgstr "URI" @@ -1554,9 +1560,15 @@ "\n" "S'està aconseguint un TinyURL..." -#, fuzzy +#, c-format +msgid "TinyURL for above: %s" +msgstr "Fes un TinyURL d'això d'aquí dalt: %s" + +msgid "Please wait while TinyURL fetches a shorter URL ..." +msgstr "Espreu mentre TinyURL obté una URL més curta..." + msgid "Only create TinyURL for URLs of this length or greater" -msgstr "Crea TinyURL per a URL així de llargues o més" +msgstr "Només crea TinyURL per a URL així de llargues o més" msgid "TinyURL (or other) address prefix" msgstr "Prefix de l'adreça TinyURL (o altra)" @@ -1567,10 +1579,9 @@ msgid "TinyURL plugin" msgstr "Connector TinyURL" -#, fuzzy msgid "When receiving a message with URL(s), use TinyURL for easier copying" msgstr "" -"Quan rebeu missagtes amb URL, feu servir TinyURL per a copiar més fàcilment" +"En rebre missagtes amb URL, s'empra TinyURL perquè sigui més fàcil copiar" msgid "Online" msgstr "En línia" @@ -1676,27 +1687,25 @@ msgid "buddy list" msgstr "llista d'amics" -#, fuzzy msgid "The certificate is self-signed and cannot be automatically checked." -msgstr "" -"No es pot comprovar el certificat que presenta «%s» atès que està auto-signat." - -#, fuzzy -msgid "The root certificate this one claims to be issued by is unknown." -msgstr "El Pidgin no coneix el certificat arrel d'aquest certificat." - -#, fuzzy +msgstr "No es pot comprovar el certificat atès que està auto-signat." + +msgid "" +"The certificate is not trusted because no certificate that can verify it is " +"currently trusted." +msgstr "" +"No es pot confiar en el certificat atès que no hi hi ha cap altre certificat " +"de confiança que el pugui verificar." + msgid "The certificate is not valid yet." -msgstr "La cadena de certificació que presenta %s no és vàlida." - -#, fuzzy +msgstr "El certificat encara no és vàlid." + msgid "The certificate has expired and should not be considered valid." -msgstr "La cadena de certificació que presenta %s no és vàlida." +msgstr "El certificat ha expirat i no s'hauria de considerar vàlid." #. Translators: "domain" refers to a DNS domain (e.g. talk.google.com) -#, fuzzy msgid "The certificate presented is not issued to this domain." -msgstr "La cadena de certificació que presenta %s no és vàlida." +msgstr "El certificat que s'ha presentat no ha estat emès per a aquest domini." msgid "" "You have no database of root certificates, so this certificate cannot be " @@ -1705,17 +1714,14 @@ "Aquest certificat no es pot validar perquè no teniu cap base de dades de " "certificats arrel." -#, fuzzy msgid "The certificate chain presented is invalid." -msgstr "La cadena de certificació que presenta %s no és vàlida." - -#, fuzzy +msgstr "La cadena de certificació que s'ha presentat no és vàlida." + msgid "The certificate has been revoked." -msgstr "Ha finalitzat la trucada." - -#, fuzzy +msgstr "El certificat ha estat revocat." + msgid "An unknown certificate error occurred." -msgstr "Hi ha hagut un error de connexió desconegut: %s." +msgstr "S'ha produït un error desconegut en el certificat." msgid "(DOES NOT MATCH)" msgstr "(NO COINCIDEIX)" @@ -1760,26 +1766,25 @@ msgid "_View Certificate..." msgstr "_Mostra el certificat..." -#, fuzzy, c-format +#, c-format msgid "The certificate for %s could not be validated." -msgstr "La cadena de certificació que presenta %s no és vàlida." +msgstr "No s'ha pogut validar el certificat de %s." # Títol de finestra (josep) #. TODO: Probably wrong. msgid "SSL Certificate Error" msgstr "Error en el certificat SSL" -#, fuzzy msgid "Unable to validate certificate" -msgstr "No s'ha pogut autenticar: %s" - -#, fuzzy, c-format +msgstr "No s'ha pogut certificar" + +#, c-format msgid "" "The certificate claims to be from \"%s\" instead. This could mean that you " "are not connecting to the service you believe you are." msgstr "" -"El certificat de «%s» sembla indicar que és de «%s». Això podria voler dir que " -"us esteu connectant a un servei diferent del que us penseu." +"El certificat indica que és de «%s». Això podria voler dir que us esteu " +"connectant a un servei diferent del que us penseu." #. Make messages #, c-format @@ -1927,6 +1932,10 @@ msgstr "El procés resoledor ha acabat sense respondre la nostra sol·licitud" #, c-format +msgid "Error converting %s to punycode: %d" +msgstr "S'ha produït un error en convertir %s a punycode: %d" + +#, c-format msgid "Thread creation failure: %s" msgstr "S'ha produït un error en crear un fil: %s" @@ -2020,18 +2029,18 @@ msgid "File transfer complete" msgstr "S'ha completat la transferència del fitxer" -#, fuzzy, c-format +#, c-format msgid "You cancelled the transfer of %s" msgstr "Heu cancel·lat la transferència de %s" msgid "File transfer cancelled" msgstr "S'ha cancel·lat la transferència del fitxer" -#, fuzzy, c-format +#, c-format msgid "%s cancelled the transfer of %s" msgstr "%s ha cancel·lat la transferència de %s" -#, fuzzy, c-format +#, c-format msgid "%s cancelled the file transfer" msgstr "%s ha cancel·lat la transferència del fitxer" @@ -2224,28 +2233,30 @@ "No codecs found. Install some GStreamer codecs found in GStreamer plugins " "packages." msgstr "" +"No s'ha trobat cap còdec. Instal·leu els còdecs del GStreamer que podeu " +"trobar en els paquests de connectors del GStreamer." msgid "" "No codecs left. Your codec preferences in fs-codecs.conf are too strict." msgstr "" - -#, fuzzy +"No hi ha cap més còdec. Les preferències dels còdecs al fitxer fs-codecs." +"conf són massa estrictes." + msgid "A non-recoverable Farsight2 error has occurred." -msgstr "Hi ha hagut un error de connexió desconegut: %s." - -#, fuzzy -msgid "Conference error." -msgstr "Conferència tancada" - -msgid "Error with your microphone." -msgstr "" - -msgid "Error with your webcam." -msgstr "" - -#, fuzzy, c-format +msgstr "S'ha produït un error no recuperable del Farsight2." + +msgid "Conference error" +msgstr "Error en la conferència" + +msgid "Error with your microphone" +msgstr "S'ha produït un error amb el micròfon" + +msgid "Error with your webcam" +msgstr "S'ha produït un error amb la càmera web" + +#, c-format msgid "Error creating session: %s" -msgstr "S'ha produït un error en crear la connexió" +msgstr "S'ha produït un error en crear la sessió: %s" msgid "Error creating conference." msgstr "S'ha produït un error en crear la conferència." @@ -2511,16 +2522,15 @@ msgid "Test plugin IPC support, as a server. This registers the IPC commands." msgstr "Connector de proves per a servidor d'IPC, que registra les ordres IPC." -#, fuzzy msgid "Hide Joins/Parts" -msgstr "Oculta els errors en entrar" +msgstr "Oculta en entrar/sortir" #. Translators: Followed by an input request a number of people msgid "For rooms with more than this many people" -msgstr "" +msgstr "Per sales amb més persones que" msgid "If user has not spoken in this many minutes" -msgstr "" +msgstr "Si l'usuari no ha parlat en" msgid "Apply hiding rules to buddies" msgstr "Aplica les normes d'ocultació als amics" @@ -3973,11 +3983,11 @@ msgid "Logo" msgstr "Logotip" -#, fuzzy, c-format +#, c-format msgid "" "%s will no longer be able to see your status updates. Do you want to " "continue?" -msgstr "Esteu segur que voleu suprimir %s de la llista d'amics?" +msgstr "%s no podrà veure l'actualització del vostre estat. Voleu continuar?" msgid "Cancel Presence Notification" msgstr "Cancel·la la notificació de presència" @@ -3996,6 +4006,9 @@ msgid "Unsubscribe" msgstr "Cancel·la la subscripció" +msgid "Initiate _Chat" +msgstr "Inicia un _xat" + msgid "Log In" msgstr "Connecta" @@ -4580,7 +4593,7 @@ msgid "configure: Configure a chat room." msgstr "configure: configura la sala de xat." -msgid "part [room]: Leave the room." +msgid "part [message]: Leave the room." msgstr "part [sala]: surt de la sala." msgid "register: Register with a chat room." @@ -4599,13 +4612,12 @@ "affiliate <owner|admin|member|outcast|none> [sobrenom1] " "[sobrenom2] ...: obtén els usuaris amb una afiliació, o els l'estableix." -#, fuzzy msgid "" "role <moderator|participant|visitor|none> [nick1] [nick2] ...: Get the " "users with a role or set users' role with the room." msgstr "" -"role <usuari> <moderator|participant|visitor|none> [sobrenom1] " -"[sobrenom2] ...: obtén els usuaris amb el rol especificat, o els l'estableix." +"role <moderator|participant|visitor|none> [sobrenom1] [sobrenom2] ...: " +"obtén els usuaris amb el rol especificat, o els l'estableix." msgid "invite <user> [message]: Invite a user to the room." msgstr "invite <usuari> [sala]: convida un usuari a la sala." @@ -5025,7 +5037,6 @@ msgid "Not expected" msgstr "Inesperat" -#, fuzzy msgid "Friendly name is changing too rapidly" msgstr "El nom amistós canvia massa de pressa" @@ -5245,19 +5256,16 @@ msgid "Send to Mobile" msgstr "Envia a un mòbil" -msgid "Initiate _Chat" -msgstr "Inicia un _xat" - msgid "SSL support is needed for MSN. Please install a supported SSL library." msgstr "L'MSN necessita SSL, instal·leu alguna biblioteca d'SSL permesa." #, c-format msgid "" "Unable to add the buddy %s because the username is invalid. Usernames must " -"be a valid email address." +"be valid email addresses." msgstr "" "No s'ha pogut afegir l'amic %s perquè el nom d'usuari no és vàlid. Els noms " -"d'usuari han de ser adreces de correu vàlides." +"d'usuari han de ser adreces de correu electròniques vàlides." msgid "Unable to Add" msgstr "No s'ha pogut afegir" @@ -5597,10 +5605,10 @@ "%s ha sol·licitat poder veure la vostra càmera web, però això encara no està " "implementat." -#, fuzzy, c-format +#, c-format msgid "%s invited you to view his/her webcam, but this is not yet supported." msgstr "" -"%s ha sol·licitat poder veure la vostra càmera web, però això encara no està " +"%s us ha convidat a veure la seva càmera web, però això encara no està " "implementat." msgid "Away From Computer" @@ -6354,9 +6362,9 @@ msgstr "Port en el servidor" #. Note to translators: %s in this string is a URL -#, fuzzy, c-format +#, c-format msgid "Received unexpected response from %s" -msgstr "S'ha rebut una resposta inesperada de " +msgstr "S'ha rebut una resposta inesperada de %s" #. username connecting too frequently msgid "" @@ -6369,9 +6377,9 @@ #. Note to translators: The first %s is a URL, the second is an #. error message. -#, fuzzy, c-format +#, c-format msgid "Error requesting %s: %s" -msgstr "S'ha produït en sol·licitar " +msgstr "S'ha produït un error en sol·licitar %s: %s" msgid "AOL does not allow your screen name to authenticate here" msgstr "AOL no permet que us autentiqueu amb aquest nom d'usuari aquí" @@ -7149,9 +7157,8 @@ msgid "C_onnect" msgstr "C_onnecta" -#, fuzzy msgid "You closed the connection." -msgstr "El servidor ha tancat la connexió" +msgstr "Heu tancat la connexió." msgid "Get AIM Info" msgstr "Obtén informació de AIM" @@ -7163,9 +7170,8 @@ msgid "Get Status Msg" msgstr "Aconsegueix el missatge d'estat" -#, fuzzy msgid "End Direct IM Session" -msgstr "S'ha establert una connexió directa de MI" +msgstr "Finalitzar la sessió de MI directa" msgid "Direct IM" msgstr "MI directa" @@ -8007,7 +8013,7 @@ msgid "File Send" msgstr "S'ha enviat el fitxer" -#, fuzzy, c-format +#, c-format msgid "%d cancelled the transfer of %s" msgstr "%d ha cancel·lat la transferència de %s" @@ -9545,7 +9551,7 @@ msgstr "Bloca invitacions a conferències i sales de xat" msgid "Use account proxy for SSL connections" -msgstr "" +msgstr "Empra un compte per al servidor intermediàri per a connexions SSL" msgid "Chat room list URL" msgstr "URL de la llista de sales de xat" @@ -9653,26 +9659,26 @@ msgid "Ignore buddy?" msgstr "Voleu ignorar l'amic?" -#, fuzzy msgid "Invalid username or password" -msgstr "El sobrenom o la contrasenya no són correctes" - -#, fuzzy +msgstr "El sobrenom o la contrasenya no són vàlides" + msgid "" "Your account has been locked due to too many failed login attempts. Please " "try logging into the Yahoo! website." msgstr "" -"El compte està blocat perquè s'ha intentat entrar massa cops. Això es pot " -"solucionar entrant al web de Yahoo!" +"S'ha blocat el vostre compte perquè s'ha intentat entrar massa cops. Entreu " +"al web de Yahoo! per solucionar això." #, c-format msgid "Unknown error 52. Reconnecting should fix this." -msgstr "" +msgstr "Error desconegut 52. Es pot sol·lucionar connectant de nou." msgid "" "Error 1013: The username you have entered is invalid. The most common cause " "of this error is entering your email address instead of your Yahoo! ID." msgstr "" +"Error 1013: el nom d'usuari no és vàlid. Pot ser que hagueu introduït la " +"vostra adreça de correu en lloc del nom d'usuari de Yahoo!" #, c-format msgid "Unknown error number %d. Logging into the Yahoo! website may fix this." @@ -9764,6 +9770,16 @@ msgid "Open Inbox" msgstr "Obre la safata d'entrada" +msgid "Can't send SMS. Unable to obtain mobile carrier." +msgstr "" +"No es poden enviar SMS, no s'ha pogut obtenir l'operador de telefonia mòbil." + +msgid "Can't send SMS. Unknown mobile carrier." +msgstr "No es poden enviar SMS, no es coneix l'operador de telefona mòbil." + +msgid "Getting mobile carrier to send the SMS." +msgstr "S'està obtenint l'operador de telefonia mòbil per a poder enviar SMS." + #. Write a local message to this conversation showing that a request for a #. * Doodle session has been made #. @@ -10456,7 +10472,6 @@ msgid "Layout" msgstr "Format" -#, fuzzy msgid "The layout of icons, name, and status of the buddy list" msgstr "El format de les icones, el nom, i l'estat de la llista d'amics" @@ -10513,9 +10528,8 @@ #. Note to translators: These two strings refer to the font and color #. of a buddy list buddy when it is online -#, fuzzy msgid "Online Text" -msgstr "Text en línia" +msgstr "Text en estar en línia" msgid "The text information for when a buddy is online" msgstr "Text informatiu per quan un amic estigui en línia" @@ -10523,18 +10537,16 @@ #. Note to translators: These two strings refer to the font and color #. of a buddy list buddy when it is away msgid "Away Text" -msgstr "Text d'absència" +msgstr "Text en estar absent" msgid "The text information for when a buddy is away" msgstr "Text informatiu per quan un amic estigui absent" #. Note to translators: These two strings refer to the font and color #. of a buddy list buddy when it is offline -#, fuzzy msgid "Offline Text" -msgstr "Text de fora de línia" - -#, fuzzy +msgstr "Text fora de línia" + msgid "The text information for when a buddy is offline" msgstr "Text informatiu per quan un amic estigui fora de línia" @@ -10559,7 +10571,6 @@ msgid "Message (Nick Said) Text" msgstr "Text del missatge (on s'hi ha dit el sobrenom)" -#, fuzzy msgid "" "The text information for when a chat has an unread message that mentions " "your nickname" @@ -11005,9 +11016,8 @@ msgid "_Group:" msgstr "_Grup:" -#, fuzzy msgid "Auto_join when account connects." -msgstr "_Entra automàticament quant el compte estigui connectat." +msgstr "_Entra automàticament quant es connecti el compte." msgid "_Remain in chat after window is closed." msgstr "Co_ntinua al xat quan la finestra es tanqui." @@ -11121,9 +11131,8 @@ msgid "/Conversation/New Instant _Message..." msgstr "/Conversa/_Missatge instantani nou..." -#, fuzzy msgid "/Conversation/Join a _Chat..." -msgstr "/Conversa/Con_vida..." +msgstr "/Conversa/Entra a un _xat..." msgid "/Conversation/_Find..." msgstr "/Conversa/_Cerca..." @@ -11514,7 +11523,7 @@ msgstr "Estonià" msgid "Basque" -msgstr "" +msgstr "Basc" msgid "Persian" msgstr "Persa" @@ -11728,6 +11737,13 @@ "primary language is English. You are welcome to post in another " "language, but the responses may be less helpful.

" msgstr "" +"Ajuda d'altres usuaris del Pidgin: support@pidgin.im
Aquesta és una llista de " +"correu pública. (arxiu)
No us podem ajudar amb connectors d'altres proveïdors.
En aquesta llista s'hi empra principalment l'anglès. Podeu escriure-" +"hi en un altre idioma, però és possible que les respostes no siguin de gaire " +"ajuda.

" #, c-format msgid "" @@ -12299,45 +12315,48 @@ "Usage: %s [OPTION]...\n" "\n" msgstr "" - -#, fuzzy +"Forma d'ús: %s [OPCIÓ]...\n" +"\n" + msgid "DIR" -msgstr "IRC" +msgstr "DIR" msgid "use DIR for config files" -msgstr "" +msgstr "empra DIR per a fitxers de configuració" msgid "print debugging messages to stdout" -msgstr "" +msgstr "escriu missatges de depuració a la sortida estàndard" msgid "force online, regardless of network status" -msgstr "" +msgstr "força estar en línia, independentment de l'estat de la xarxa" msgid "display this help and exit" -msgstr "" +msgstr "mostra aquesta ajuda i surt" # FIXME: entrades/registres? -#, fuzzy msgid "allow multiple instances" -msgstr "Permet diverses entrades simultànies" +msgstr "permet diverses instàncies" msgid "don't automatically login" -msgstr "" +msgstr "no entra als comptes" msgid "NAME" -msgstr "" +msgstr "NOM" msgid "" "enable specified account(s) (optional argument NAME\n" " specifies account(s) to use, separated by commas.\n" " Without this only the first account will be enabled)." msgstr "" +"habilita els comptes especificats (l'argument opcional NAME especifica\n" +" els comptes a emprar, separats per comes. Sense això\n" +" només s'habilitarà el primer compte)." msgid "X display to use" -msgstr "" +msgstr "pantalla d'X a emprar" msgid "display the current version and exit" -msgstr "" +msgstr "mostra la versió actual i surt" # FIXME: backtrace -> traça (bug-buddy) ? #, c-format @@ -12394,7 +12413,7 @@ msgstr "%s vol iniciar una sessió de vídeo." msgid "Incoming Call" -msgstr "" +msgstr "Trucada entrant" msgid "_Pause" msgstr "_Pausa" @@ -12576,50 +12595,54 @@ msgid "Pounce Target" msgstr "Objectiu de l'avís" -#, c-format msgid "Started typing" msgstr "Hagi començat a escriure" -#, c-format msgid "Paused while typing" msgstr "S'aturi mentre tecleja" -#, c-format msgid "Signed on" msgstr "Es connecti" -#, c-format msgid "Returned from being idle" msgstr "Torna a estar actiu" -#, c-format msgid "Returned from being away" msgstr "Torni a estar present" -#, c-format msgid "Stopped typing" msgstr "Pari d'escriure" -#, c-format msgid "Signed off" msgstr "Es desconnecti" -#, c-format msgid "Became idle" msgstr "Passi a inactiu" -#, c-format msgid "Went away" msgstr "En estar absent" -#, c-format msgid "Sent a message" msgstr "Envia un missatge" -#, c-format msgid "Unknown.... Please report this!" msgstr "Esdeveniment d'avís desconegut, informeu-nos-en." +msgid "(Custom)" +msgstr "(Personalitzat)" + +msgid "(Default)" +msgstr "(Predeterminat)" + +msgid "The default Pidgin sound theme" +msgstr "El tema de sons predeterminat del pidgin" + +msgid "The default Pidgin buddy list theme" +msgstr "El tema per a la llista d'amics predeterminat del Pidgin" + +msgid "The default Pidgin status icon theme" +msgstr "El tema de les icones d'estat predeterminat del Pidgin" + msgid "Theme failed to unpack." msgstr "No s'ha pogut desempaquetar el tema." @@ -12764,14 +12787,16 @@ msgid "Cannot start browser configuration program." msgstr "No s'ha pogut iniciar el programa de configuració del navegador." -#, fuzzy msgid "Disabled" -msgstr "_Inhabilita" +msgstr "Inhabilitat" #, c-format msgid "Use _automatically detected IP address: %s" msgstr "Empra l'_adreça IP detectada automàticament: %s" +msgid "ST_UN server:" +msgstr "Servidor ST_UN:" + msgid "Example: stunserver.org" msgstr "Exemple: stunserver.org" @@ -12797,9 +12822,8 @@ msgid "Relay Server (TURN)" msgstr "Servidor repetidor (TURN)" -#, fuzzy msgid "_TURN server:" -msgstr "Servidor ST_UN:" +msgstr "Servidor _TURN:" msgid "Proxy Server & Browser" msgstr "Servidor intermediari i navegador" @@ -14372,35 +14396,29 @@ "Aquest connector permet a l'usuari personalitzar els formats de les marques " "horàries de les converses i dels registres." -#, fuzzy msgid "Audio" -msgstr "Auto" - -#, fuzzy +msgstr "Àudio" + msgid "Video" msgstr " Vídeo" msgid "Output" -msgstr "" - -#, fuzzy +msgstr "Sortida" + msgid "_Plugin" -msgstr "Connectors" - -#, fuzzy +msgstr "_Connectors" + msgid "_Device" -msgstr "Dispositiu" +msgstr "_Dispositiu" msgid "Input" -msgstr "" - -#, fuzzy +msgstr "Entrada" + msgid "P_lugin" -msgstr "Connectors" - -#, fuzzy +msgstr "C_onnectors" + msgid "D_evice" -msgstr "Dispositiu" +msgstr "D_ispositiu" #. *< magic #. *< major version @@ -14411,18 +14429,19 @@ #. *< dependencies #. *< priority #. *< id -#, fuzzy msgid "Voice/Video Settings" -msgstr "Edita els paràmetres" +msgstr "Configuració del so/vídeo" #. *< name #. *< version msgid "Configure your microphone and webcam." -msgstr "" +msgstr "Configureu el micròfon i la càmera web." #. *< summary msgid "Configure microphone and webcam settings for voice/video calls." msgstr "" +"Configureu els paràmetres del micròfon i la càmera web per a trucades de veu/" +"vídeo." msgid "Opacity:" msgstr "Opacitat:" @@ -14481,9 +14500,6 @@ "\n" "* Nota: aquest connector requereix Windows 2000 o superior." -msgid "GTK+ Runtime Version" -msgstr "Versió del mòdul d'execució de GTK+" - #. Autostart msgid "Startup" msgstr "Inicialització" @@ -14492,6 +14508,10 @@ msgid "_Start %s on Windows startup" msgstr "_Inicia el %s en iniciar Windows" +# FIXME: entrades/registres? +msgid "Allow multiple instances" +msgstr "Permet diverses instàncies" + msgid "_Dockable Buddy List" msgstr "Llista _d'amics acoblable" @@ -14553,6 +14573,9 @@ msgid "This plugin is useful for debbuging XMPP servers or clients." msgstr "Aquest connector és útil per a depurar servidors i clients XMPP." +#~ msgid "GTK+ Runtime Version" +#~ msgstr "Versió del mòdul d'execució de GTK+" + #~ msgid "Calling ... " #~ msgstr "S'està trucant..." @@ -14796,9 +14819,6 @@ #~ msgid "Could not write" #~ msgstr "No s'ha pogut escriure" -#~ msgid "Could not connect" -#~ msgstr "No s'ha pogut connectar" - #~ msgid "Could not create listen socket" #~ msgstr "No s'ha pogut crear el sòcol per a escoltar"