Mercurial > pidgin
changeset 29238:e4884dbf0c02
propagate from branch 'im.pidgin.cpw.maiku.media_refactor' (head 69be24ee75b8f957cfee3cfb7fd71721078b7f81)
to branch 'im.pidgin.pidgin.next.minor' (head 25b1417fdd880468e1d6f027f8f325b4c56e9f02)
author | maiku@pidgin.im |
---|---|
date | Wed, 11 Nov 2009 03:07:21 +0000 |
parents | f93ac891ff01 (diff) 1eb68d854dfc (current diff) |
children | 9da83d5f48c3 |
files | ChangeLog.API libpurple/Makefile.am libpurple/dnsquery.c libpurple/dnssrv.c libpurple/media.c libpurple/protocols/jabber/JEPS libpurple/protocols/jabber/jingle/rtp.c po/POTFILES.in |
diffstat | 181 files changed, 13920 insertions(+), 2014 deletions(-) [+] |
line wrap: on
line diff
--- a/COPYRIGHT Mon Nov 02 23:14:33 2009 +0000 +++ b/COPYRIGHT Wed Nov 11 03:07:21 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 <js-pidgin@webkeks.org> -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
--- a/ChangeLog Mon Nov 02 23:14:33 2009 +0000 +++ b/ChangeLog Wed Nov 11 03:07:21 2009 +0000 @@ -1,3 +1,4 @@ + Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul version 2.7.0 (??/??/????): @@ -12,17 +13,39 @@ box to a "Developer Information" dialog accessible on the Help menu. * Moved the Translator information from the About box to a "Translator Information" dialog accessible on the Help menu. + * Use GtkStatusIcon for the docklet, providing better integration in + notification area. -version 2.6.3 (??/??/20??): +version 2.6.4 (??/??/20??): + libpurple: + * Actually emit the hold signal for media calls. + * Added "MXit" protocol plugin, supported and maintained by the MXit folks + themselves (MXit Lifestyle (Pty) Ltd.) + General: * New 'plugins' sub-command to 'debug' command (i.e. '/debug plugins') 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 @@ -30,6 +53,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: @@ -39,6 +65,7 @@ * Add support for adding OCS and Sametime buddies. OCS users are added as "ocs/user@domain.tld" and Sametime users are added as "ibm/sametime_id". (Jason Cohen) + Finch: * The TinyURL plugin now creates shorter URLs for long non-conversation URLs, e.g. URLs to open Inbox in Yahoo/MSN protocols, or the Yahoo @@ -46,7 +73,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:
--- a/ChangeLog.API Mon Nov 02 23:14:33 2009 +0000 +++ b/ChangeLog.API Wed Nov 11 03:07:21 2009 +0000 @@ -21,6 +21,9 @@ * pidgin_dialogs_developers (should not be used by anything but Pidgin) * pidgin_dialogs_translators (should not be used by anything but Pidgin) +version 2.6.3 (10/16/2009): + No changes + version 2.6.2 (09/05/2009): Perl: Added:
--- a/ChangeLog.win32 Mon Nov 02 23:14:33 2009 +0000 +++ b/ChangeLog.win32 Wed Nov 11 03:07:21 2009 +0000 @@ -1,6 +1,12 @@ version 2.7.0 (??/??/????): * Minimum required GTK+ version increased to 2.14.0 + * Private GTK+ Runtime now used (GTK+ Installer no longer supported) * Win9x no longer supported. + * Crash Report files (pidgin.RPT) are now generated in the ~/.purple + directory instead of the installation directory. + +version 2.6.3 (10/16/2009): + * No changes version 2.6.2 (09/05/2009): * No changes
--- a/Makefile.mingw Mon Nov 02 23:14:33 2009 +0000 +++ b/Makefile.mingw Wed Nov 11 03:07:21 2009 +0000 @@ -37,6 +37,7 @@ ) STRIPPED_RELEASE_DIR = $(PIDGIN_TREE_TOP)/pidgin-$(PIDGIN_VERSION)-win32bin +DEBUG_SYMBOLS_DIR = $(PIDGIN_TREE_TOP)/pidgin-$(PIDGIN_VERSION)-dbgsym # Any *.dll or *.exe files included in win32-install-dir that we don't compile @@ -71,7 +72,7 @@ #build an expression for `find` to use to ignore the above files EXTERNAL_DLLS_FIND_EXP = $(patsubst %,-o -name %,$(EXTERNAL_DLLS)) -.PHONY: all docs install installer installer_nogtk installer_debug installers clean uninstall create_release_install_dir +.PHONY: all docs install installer installer_offline installer_zip debug_symbols_zip installers clean uninstall create_release_install_dir generate_translations_installer_include $(PIDGIN_REVISION_H) $(PIDGIN_REVISION_RAW_TXT) all: $(PIDGIN_CONFIG_H) $(PIDGIN_REVISION_H) $(MAKE) -C $(PURPLE_TOP) -f $(MINGW_MAKEFILE) @@ -89,29 +90,41 @@ $(MAKE) -C share/ca-certs -f $(MINGW_MAKEFILE) install $(MAKE) -C share/sounds -f $(MINGW_MAKEFILE) install +generate_translations_installer_include: create_release_install_dir + rm -f pidgin/win32/nsis/pidgin-translations.nsh + find $(STRIPPED_RELEASE_DIR)/locale -maxdepth 1 -mindepth 1 \ + -exec basename {} ';' \ + | sed -e s/^/\!insertmacro\ LANG_SECTION\ \"/ -e s/$$/\"/ \ + > pidgin/win32/nsis/pidgin-translations.nsh + create_release_install_dir: install rm -rf $(STRIPPED_RELEASE_DIR) cp -R $(PIDGIN_INSTALL_DIR) $(STRIPPED_RELEASE_DIR) find $(STRIPPED_RELEASE_DIR) \( -name '*.dll' -o -name '*.exe' \) \ - -not \( -false $(EXTERNAL_DLLS_FIND_EXP) \) -exec $(STRIP) --strip-unneeded {} ';' + -not \( -false $(EXTERNAL_DLLS_FIND_EXP) \) \ + -exec $(STRIP) --strip-unneeded {} ';' -installer: create_release_install_dir - $(MAKENSIS) $(MAKENSISOPT)V3 $(MAKENSISOPT)DPIDGIN_VERSION="$(PIDGIN_VERSION)" $(MAKENSISOPT)DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" $(MAKENSISOPT)DWITH_GTK $(MAKENSISOPT)DPIDGIN_INSTALL_DIR="$(STRIPPED_RELEASE_DIR)" $(MAKENSISOPT)DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi +installer: create_release_install_dir generate_translations_installer_include + $(MAKENSIS) $(MAKENSISOPT)V3 $(MAKENSISOPT)DPIDGIN_VERSION="$(PIDGIN_VERSION)" $(MAKENSISOPT)DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" $(MAKENSISOPT)DPIDGIN_INSTALL_DIR="$(STRIPPED_RELEASE_DIR)" $(MAKENSISOPT)DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi mv pidgin/win32/nsis/pidgin-$(PIDGIN_VERSION).exe ./ -installer_nogtk: create_release_install_dir - $(MAKENSIS) $(MAKENSISOPT)V3 $(MAKENSISOPT)DPIDGIN_VERSION="$(PIDGIN_VERSION)" $(MAKENSISOPT)DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" $(MAKENSISOPT)DPIDGIN_INSTALL_DIR="$(STRIPPED_RELEASE_DIR)" $(MAKENSISOPT)DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi - mv pidgin/win32/nsis/pidgin-$(PIDGIN_VERSION)-no-gtk.exe ./ - -installer_debug: install - $(MAKENSIS) $(MAKENSISOPT)V3 $(MAKENSISOPT)DPIDGIN_VERSION="$(PIDGIN_VERSION)" $(MAKENSISOPT)DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" $(MAKENSISOPT)DPIDGIN_INSTALL_DIR="$(PIDGIN_INSTALL_DIR)" $(MAKENSISOPT)DDEBUG $(MAKENSISOPT)DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi - mv pidgin/win32/nsis/pidgin-$(PIDGIN_VERSION)-debug.exe ./ +installer_offline: create_release_install_dir generate_translations_installer_include debug_symbols_zip + $(MAKENSIS) $(MAKENSISOPT)V3 $(MAKENSISOPT)DPIDGIN_VERSION="$(PIDGIN_VERSION)" $(MAKENSISOPT)DPIDGIN_PRODUCT_VERSION="$(PIDGIN_PRODUCT_VERSION)" $(MAKENSISOPT)DOFFLINE_INSTALLER $(MAKENSISOPT)DPIDGIN_INSTALL_DIR="$(STRIPPED_RELEASE_DIR)" $(MAKENSISOPT)DGTK_INSTALL_VERSION="$(GTK_INSTALL_VERSION)" pidgin/win32/nsis/pidgin-installer.nsi + mv pidgin/win32/nsis/pidgin-$(PIDGIN_VERSION)-offline.exe ./ installer_zip: create_release_install_dir rm -f pidgin-$(PIDGIN_VERSION)-win32-bin.zip zip -9 -r pidgin-$(PIDGIN_VERSION)-win32-bin.zip $(STRIPPED_RELEASE_DIR) -installers: installer installer_nogtk installer_debug installer_zip +debug_symbols_zip: install + rm -rf $(DEBUG_SYMBOLS_DIR) $(DEBUG_SYMBOLS_DIR).zip + mkdir $(DEBUG_SYMBOLS_DIR) + tar -cf - `find $(PIDGIN_INSTALL_DIR) \( -name '*.dll' -o -name '*.exe' \) \ + -not \( -false $(EXTERNAL_DLLS_FIND_EXP) \) -print` \ + | tar --strip 2 --xform s/$$/.dbgsym/ -xC $(DEBUG_SYMBOLS_DIR) -f - + zip -9 -r $(DEBUG_SYMBOLS_DIR).zip $(DEBUG_SYMBOLS_DIR) + +installers: installer installer_offline debug_symbols_zip installer_zip Doxyfile.mingw: Doxyfile.in sed -e "s/@PACKAGE@/pidgin/" -e "s/@VERSION@/$(PIDGIN_VERSION)/" -e "s/@top_srcdir@/$(PIDGIN_TREE_TOP)/g" -e "s/@enable_dot@/NO/" Doxyfile.in > Doxyfile.mingw @@ -125,13 +138,12 @@ $(MAKE) -C $(PIDGIN_TOP) -f $(MINGW_MAKEFILE) clean $(MAKE) -C $(PURPLE_TOP) -f $(MINGW_MAKEFILE) clean $(MAKE) -C share/ca-certs -f $(MINGW_MAKEFILE) clean - rm -f $(PIDGIN_CONFIG_H) $(PIDGIN_REVISION_H) $(PIDGIN_REVISION_RAW_TXT) ./VERSION pidgin-$(PIDGIN_VERSION)*.exe pidgin-$(PIDGIN_VERSION)-win32-bin.zip + rm -f $(PIDGIN_CONFIG_H) $(PIDGIN_REVISION_H) $(PIDGIN_REVISION_RAW_TXT) ./VERSION pidgin-$(PIDGIN_VERSION)*.exe pidgin-$(PIDGIN_VERSION)-win32-bin.zip $(DEBUG_SYMBOLS_DIR).zip rm -rf doc/html Doxyfile.mingw uninstall: - rm -rf $(PURPLE_INSTALL_PERL_DIR) $(PIDGIN_INSTALL_PLUGINS_DIR) $(PURPLE_INSTALL_PO_DIR) $(PIDGIN_INSTALL_DIR) $(STRIPPED_RELEASE_DIR) + rm -rf $(PURPLE_INSTALL_PERL_DIR) $(PIDGIN_INSTALL_PLUGINS_DIR) $(PURPLE_INSTALL_PO_DIR) $(PIDGIN_INSTALL_DIR) $(STRIPPED_RELEASE_DIR) $(DEBUG_SYMBOLS_DIR) rm -f ./VERSION include $(PIDGIN_COMMON_TARGETS) -.PHONY: $(PIDGIN_REVISION_H) $(PIDGIN_REVISION_RAW_TXT)
--- a/NEWS Mon Nov 02 23:14:33 2009 +0000 +++ b/NEWS Wed Nov 11 03:07:21 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
--- a/autogen.sh Mon Nov 02 23:14:33 2009 +0000 +++ b/autogen.sh Wed Nov 11 03:07:21 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"}
--- a/configure.ac Mon Nov 02 23:14:33 2009 +0000 +++ b/configure.ac Wed Nov 11 03:07:21 2009 +0000 @@ -1078,7 +1078,7 @@ fi if test "x$STATIC_PRPLS" = "xall" ; then - STATIC_PRPLS="bonjour gg irc jabber msn myspace novell oscar qq sametime silc simple yahoo zephyr" + STATIC_PRPLS="bonjour gg irc jabber msn myspace mxit novell oscar qq sametime silc simple yahoo zephyr" fi if test "x$have_meanwhile" != "xyes" ; then STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/sametime//'` @@ -1135,6 +1135,7 @@ msn) static_msn=yes ;; msnp9) static_msn=yes ;; myspace) static_myspace=yes ;; + mxit) static_mxit=yes ;; novell) static_novell=yes ;; oscar) static_oscar=yes ;; aim) static_oscar=yes ;; @@ -1155,6 +1156,7 @@ AM_CONDITIONAL(STATIC_JABBER, test "x$static_jabber" = "xyes") AM_CONDITIONAL(STATIC_MSN, test "x$static_msn" = "xyes") AM_CONDITIONAL(STATIC_MYSPACE, test "x$static_myspace" = "xyes") +AM_CONDITIONAL(STATIC_MXIT, test "x$static_mxit" = "xyes") AM_CONDITIONAL(STATIC_NOVELL, test "x$static_novell" = "xyes") AM_CONDITIONAL(STATIC_OSCAR, test "x$static_oscar" = "xyes") AM_CONDITIONAL(STATIC_QQ, test "x$static_qq" = "xyes") @@ -1169,7 +1171,7 @@ AC_ARG_WITH(dynamic_prpls, [AC_HELP_STRING([--with-dynamic-prpls], [specify which protocols to build dynamically])], [DYNAMIC_PRPLS=`echo $withval | $sedpath 's/,/ /g'`]) if test "x$DYNAMIC_PRPLS" = "xall" ; then - DYNAMIC_PRPLS="bonjour gg irc jabber msn myspace novell oscar qq sametime silc simple yahoo zephyr" + DYNAMIC_PRPLS="bonjour gg irc jabber msn myspace mxit novell oscar qq sametime silc simple yahoo zephyr" fi if test "x$have_meanwhile" != "xyes"; then DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/sametime//'` @@ -1196,6 +1198,7 @@ msn) dynamic_msn=yes ;; msnp9) dynamic_msn=yes ;; myspace) dynamic_myspace=yes ;; + mxit) dynamic_mxit=yes ;; novell) dynamic_novell=yes ;; null) dynamic_null=yes ;; oscar) dynamic_oscar=yes ;; @@ -2529,6 +2532,7 @@ libpurple/protocols/msn/Makefile libpurple/protocols/msnp9/Makefile libpurple/protocols/myspace/Makefile + libpurple/protocols/mxit/Makefile libpurple/protocols/novell/Makefile libpurple/protocols/null/Makefile libpurple/protocols/oscar/Makefile
--- a/finch/gntconn.c Mon Nov 02 23:14:33 2009 +0000 +++ b/finch/gntconn.c Wed Nov 11 03:07:21 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
--- a/finch/gntconv.c Mon Nov 02 23:14:33 2009 +0000 +++ b/finch/gntconv.c Wed Nov 11 03:07:21 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()
--- a/finch/libgnt/gntentry.c Mon Nov 02 23:14:33 2009 +0000 +++ b/finch/libgnt/gntentry.c Wed Nov 11 03:07:21 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));
--- a/finch/libgnt/gnttextview.c Mon Nov 02 23:14:33 2009 +0000 +++ b/finch/libgnt/gnttextview.c Wed Nov 11 03:07:21 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;
--- a/finch/libgnt/gnttree.c Mon Nov 02 23:14:33 2009 +0000 +++ b/finch/libgnt/gnttree.c Wed Nov 11 03:07:21 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)
--- a/libpurple/Makefile.am Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/Makefile.am Wed Nov 11 03:07:21 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 \
--- a/libpurple/account.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/account.c Wed Nov 11 03:07:21 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) {
--- a/libpurple/certificate.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/certificate.c Wed Nov 11 03:07:21 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) {
--- a/libpurple/cipher.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/cipher.c Wed Nov 11 03:07:21 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 <glib.h> -#include <string.h> -#include <stdio.h> - #include "internal.h" #include "cipher.h" #include "dbus-maybe.h"
--- a/libpurple/dnsquery.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/dnsquery.c Wed Nov 11 03:07:21 2009 +0000 @@ -295,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 @@ -325,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) {
--- a/libpurple/dnssrv.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/dnssrv.c Wed Nov 11 03:07:21 2009 +0000 @@ -263,6 +263,57 @@ #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) @@ -281,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); @@ -355,16 +402,18 @@ 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)); - if (query.type == T_TXT) - write(out, ret->data, sizeof(PurpleTxtResponse)); + 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_to_parent(in, out, &l, sizeof(l)); + write_to_parent(in, out, response->content, l); + } g_free(ret->data); ret = g_list_remove(ret, ret->data); @@ -431,21 +480,38 @@ PurpleTxtCallback cb = query_data->cb.txt; ssize_t red; purple_debug_info("dnssrv","found %d TXT entries\n", size); - res = g_new0(PurpleTxtResponse, 1); for (i = 0; i < size; i++) { - red = read(source, res, sizeof(PurpleTxtResponse)); - if (red != sizeof(PurpleTxtResponse)) { + gsize len; + + red = read(source, &len, sizeof(len)); + if (red != sizeof(len)) { purple_debug_error("dnssrv","unable to read txt " - "response: %s\n", g_strerror(errno)); + "response length: %s\n", g_strerror(errno)); size = 0; - g_free(res); g_list_foreach(responses, (GFunc)purple_txt_response_destroy, NULL); g_list_free(responses); responses = NULL; break; } + + res = g_new0(PurpleTxtResponse, 1); + res->content = g_new0(gchar, len); + + red = read(source, res->content, len); + if (red != len) { + purple_debug_error("dnssrv","unable to read txt " + "response: %s\n", g_strerror(errno)); + size = 0; + purple_txt_response_destroy(res); + g_list_foreach(responses, (GFunc)purple_txt_response_destroy, NULL); + g_list_free(responses); + responses = NULL; + break; + } + responses = g_list_prepend(responses, res); } + responses = g_list_reverse(responses); cb(responses, query_data->extradata); } else { purple_debug_error("dnssrv", "type unknown of DNS result entry; errno is %i\n", errno); @@ -790,6 +856,7 @@ internal_query.type = T_TXT; strncpy(internal_query.query, query, 255); + internal_query.query[255] = '\0'; if (write(in[1], &internal_query, sizeof(internal_query)) < 0) purple_debug_error("dnssrv", "Could not write to TXT resolver\n");
--- a/libpurple/ft.h Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/ft.h Wed Nov 11 03:07:21 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. *
--- a/libpurple/plugins/perl/Makefile.mingw Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/plugins/perl/Makefile.mingw Wed Nov 11 03:07:21 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
--- a/libpurple/plugins/perl/common/Makefile.mingw Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/plugins/perl/common/Makefile.mingw Wed Nov 11 03:07:21 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
--- a/libpurple/plugins/perl/perl-handlers.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/plugins/perl/perl-handlers.c Wed Nov 11 03:07:21 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); }
--- a/libpurple/protocols/Makefile.am Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/Makefile.am Wed Nov 11 03:07:21 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)
--- a/libpurple/protocols/bonjour/mdns_avahi.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/bonjour/mdns_avahi.c Wed Nov 11 03:07:21 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) {
--- a/libpurple/protocols/jabber/JEPS Mon Nov 02 23:14:33 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 -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/XEPS Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,4 @@ +0060: NEED + Pub-Sub +0080: NEED (Do we?) + Geographic Location Information
--- a/libpurple/protocols/jabber/auth.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/auth.c Wed Nov 11 03:07:21 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, "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", -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);
--- a/libpurple/protocols/jabber/buddy.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/buddy.c Wed Nov 11 03:07:21 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) {
--- a/libpurple/protocols/jabber/caps.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/caps.c Wed Nov 11 03:07:21 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)
--- a/libpurple/protocols/jabber/chat.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/chat.c Wed Nov 11 03:07:21 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)
--- a/libpurple/protocols/jabber/chat.h Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/chat.h Wed Nov 11 03:07:21 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);
--- a/libpurple/protocols/jabber/disco.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/disco.c Wed Nov 11 03:07:21 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... */ } }
--- a/libpurple/protocols/jabber/google.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/google.c Wed Nov 11 03:07:21 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); }
--- a/libpurple/protocols/jabber/iq.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/iq.c Wed Nov 11 03:07:21 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;
--- a/libpurple/protocols/jabber/jabber.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.c Wed Nov 11 03:07:21 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 <proceed/>\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 || @@ -3488,9 +3496,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();
--- a/libpurple/protocols/jabber/jabber.h Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.h Wed Nov 11 03:07:21 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_ */
--- a/libpurple/protocols/jabber/jingle/jingle.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/jingle/jingle.c Wed Nov 11 03:07:21 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);
--- a/libpurple/protocols/jabber/jingle/rtp.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/jingle/rtp.c Wed Nov 11 03:07:21 2009 +0000 @@ -832,6 +832,65 @@ g_object_unref(session); break; } + case JINGLE_DESCRIPTION_INFO: { + JingleSession *session = + jingle_content_get_session(content); + xmlnode *description = xmlnode_get_child( + xmlcontent, "description"); + GList *codecs, *iter, *iter2, *remote_codecs = + jingle_rtp_parse_codecs(description); + gchar *name = jingle_content_get_name(content); + gchar *remote_jid = + jingle_session_get_remote_jid(session); + PurpleMedia *media; + + media = jingle_rtp_get_media(session); + + /* + * This may have problems if description-info is + * received without the optional parameters for a + * codec with configuration info (such as THEORA + * or H264). The local configuration info may be + * set for the remote codec. + * + * As of 2.6.3 there's no API to support getting + * the remote codecs specifically, just the + * intersection. Another option may be to cache + * the remote codecs received in initiate/accept. + */ + codecs = purple_media_get_codecs(media, name); + + for (iter = codecs; iter; iter = g_list_next(iter)) { + guint id; + + id = purple_media_codec_get_id(iter->data); + iter2 = remote_codecs; + + for (; iter2; iter2 = g_list_next(iter2)) { + if (purple_media_codec_get_id( + iter2->data) != id) + continue; + + g_object_unref(iter->data); + iter->data = iter2->data; + remote_codecs = g_list_delete_link( + remote_codecs, iter2); + break; + } + } + + codecs = g_list_concat(codecs, remote_codecs); + g_list_free (remote_codecs); + + purple_media_set_remote_codecs(media, + name, remote_jid, codecs); + + purple_media_codec_list_free (codecs); + g_free(remote_jid); + g_free(name); + g_object_unref(session); + break; + } default: break; }
--- a/libpurple/protocols/jabber/libxmpp.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Wed Nov 11 03:07:21 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);
--- a/libpurple/protocols/jabber/message.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/message.c Wed Nov 11 03:07:21 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); }
--- a/libpurple/protocols/jabber/oob.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/oob.c Wed Nov 11 03:07:21 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); }
--- a/libpurple/protocols/jabber/presence.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/presence.c Wed Nov 11 03:07:21 2009 +0000 @@ -480,7 +480,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; @@ -522,7 +522,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;
--- a/libpurple/protocols/jabber/roster.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/roster.c Wed Nov 11 03:07:21 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); }
--- a/libpurple/protocols/jabber/si.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/si.c Wed Nov 11 03:07:21 2009 +0000 @@ -1357,7 +1357,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"); } @@ -1389,7 +1389,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"); } @@ -1401,7 +1401,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"); }
--- a/libpurple/protocols/jabber/useravatar.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/jabber/useravatar.c Wed Nov 11 03:07:21 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;
--- a/libpurple/protocols/msn/contact.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/msn/contact.c Wed Nov 11 03:07:21 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);
--- a/libpurple/protocols/msn/msn.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/msn/msn.c Wed Nov 11 03:07:21 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))
--- a/libpurple/protocols/msn/msn.h Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/msn/msn.h Wed Nov 11 03:07:21 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);
--- a/libpurple/protocols/msn/nexus.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/msn/nexus.c Wed Nov 11 03:07:21 2009 +0000 @@ -385,7 +385,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);
--- a/libpurple/protocols/msn/notification.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/msn/notification.c Wed Nov 11 03:07:21 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);
--- a/libpurple/protocols/msn/oim.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/msn/oim.c Wed Nov 11 03:07:21 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; }
--- a/libpurple/protocols/msn/userlist.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/msn/userlist.c Wed Nov 11 03:07:21 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/Makefile.am Wed Nov 11 03:07:21 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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/Makefile.mingw Wed Nov 11 03:07:21 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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/actions.c Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,437 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- handle MXit plugin actions -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "purple.h" + +#include "protocol.h" +#include "mxit.h" +#include "roster.h" +#include "actions.h" +#include "splashscreen.h" +#include "cipher.h" +#include "profile.h" + + +/* MXit Moods */ +static const char* moods[] = { + /* 0 */ "None", + /* 1 */ "Angry", + /* 2 */ "Excited", + /* 3 */ "Grumpy", + /* 4 */ "Happy", + /* 5 */ "In Love", + /* 6 */ "Invincible", + /* 7 */ "Sad", + /* 8 */ "Hot", + /* 9 */ "Sick", + /* 10 */ "Sleepy" +}; + + +/*------------------------------------------------------------------------ + * The user has selected to change their current mood. + * + * @param gc The connection object + * @param fields The fields from the request pop-up + */ +static void mxit_cb_set_mood( PurpleConnection* gc, PurpleRequestFields* fields ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + int mood = purple_request_fields_get_choice( fields, "mood" ); + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_set_mood (%i)\n", mood ); + + if ( !PURPLE_CONNECTION_IS_VALID( gc ) ) { + purple_debug_error( MXIT_PLUGIN_ID, "Unable to set mood; account offline.\n" ); + return; + } + + /* Save the new mood in session */ + session->mood = mood; + + /* now send the update to MXit */ + mxit_send_mood( session, mood ); +} + + +/*------------------------------------------------------------------------ + * Create and display the mood selection window to the user. + * + * @param action The action object + */ +static void mxit_cb_action_mood( PurplePluginAction* action ) +{ + PurpleConnection* gc = (PurpleConnection*) action->context; + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + + PurpleRequestFields* fields = NULL; + PurpleRequestFieldGroup* group = NULL; + PurpleRequestField* field = NULL; + unsigned int i = 0; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_action_mood\n" ); + + fields = purple_request_fields_new(); + group = purple_request_field_group_new( NULL ); + purple_request_fields_add_group( fields, group ); + + /* show current mood */ + field = purple_request_field_string_new( "current", _( "Current Mood" ), _( moods[session->mood] ), FALSE ); + purple_request_field_string_set_editable( field, FALSE ); /* current mood field is not editable */ + purple_request_field_group_add_field( group, field ); + + /* add all moods to list */ + field = purple_request_field_choice_new( "mood", _( "New Mood" ), 0 ); + for ( i = 0; i < ARRAY_SIZE( moods ); i++ ) { + purple_request_field_choice_add( field, _( moods[i] ) ); + } + purple_request_field_set_required( field, TRUE ); + purple_request_field_choice_set_default_value( field, session->mood ); + purple_request_field_group_add_field( group, field ); + + /* (reference: "libpurple/request.h") */ + purple_request_fields( gc, _( "Mood" ), _( "Change your Mood" ), _( "How do you feel right now?" ), fields, _( "Set" ), + G_CALLBACK( mxit_cb_set_mood ), _( "Cancel" ), NULL, purple_connection_get_account( gc ), NULL, NULL, gc ); +} + + +/*------------------------------------------------------------------------ + * The user has selected to change their profile. + * + * @param gc The connection object + * @param fields The fields from the request pop-up + */ +static void mxit_cb_set_profile( PurpleConnection* gc, PurpleRequestFields* fields ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + PurpleRequestField* field = NULL; + const char* pin = NULL; + const char* pin2 = NULL; + const char* name = NULL; + const char* bday = NULL; + const char* err = NULL; + int len; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_set_profile\n" ); + + if ( !PURPLE_CONNECTION_IS_VALID( gc ) ) { + purple_debug_error( MXIT_PLUGIN_ID, "Unable to update profile; account offline.\n" ); + return; + } + + /* validate pin */ + pin = purple_request_fields_get_string( fields, "pin" ); + if ( !pin ) { + err = "The PIN you entered is invalid."; + goto out; + } + len = strlen( pin ); + if ( ( len < 4 ) || ( len > 10 ) ) { + err = "The PIN you entered has an invalid length [4-10]."; + goto out; + } + for ( i = 0; i < len; i++ ) { + if ( !g_ascii_isdigit( pin[i] ) ) { + err = "The PIN is invalid. It should only consist of digits [0-9]."; + goto out; + } + } + pin2 = purple_request_fields_get_string( fields, "pin2" ); + if ( ( !pin2 ) || ( strcmp( pin, pin2 ) != 0 ) ) { + err = "The two PINs you entered does not match."; + goto out; + } + + /* validate name */ + name = purple_request_fields_get_string( fields, "name" ); + if ( ( !name ) || ( strlen( name ) < 3 ) ) { + err = "The name you entered is invalid."; + goto out; + } + + /* validate birthdate */ + bday = purple_request_fields_get_string( fields, "bday" ); + if ( ( !bday ) || ( strlen( bday ) < 10 ) || ( !validateDate( bday ) ) ) { + err = "The birthday you entered is invalid. The correct format is: 'YYYY-MM-DD'."; + goto out; + } + +out: + if ( !err ) { + struct MXitProfile* profile = session->profile; + GString* attributes = g_string_sized_new( 128 ); + char attrib[512]; + unsigned int acount = 0; + + /* all good, so we can now update the profile */ + + /* update pin */ + purple_account_set_password( session->acc, pin ); + g_free( session->encpwd ); + session->encpwd = mxit_encrypt_password( session ); + + /* update name */ + g_strlcpy( profile->nickname, name, sizeof( profile->nickname ) ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_FULLNAME, CP_PROF_TYPE_UTF8, profile->nickname ); + g_string_append( attributes, attrib ); + acount++; + + /* update hidden */ + field = purple_request_fields_get_field( fields, "hidden" ); + profile->hidden = purple_request_field_bool_get_value( field ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_HIDENUMBER, CP_PROF_TYPE_BOOL, ( profile->hidden ) ? "1" : "0" ); + g_string_append( attributes, attrib ); + acount++; + + /* update birthday */ + strcpy( profile->birthday, bday ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_BIRTHDATE, CP_PROF_TYPE_UTF8, profile->birthday ); + g_string_append( attributes, attrib ); + acount++; + + /* update gender */ + if ( purple_request_fields_get_choice( fields, "male" ) == 0 ) + profile->male = FALSE; + else + profile->male = TRUE; + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_GENDER, CP_PROF_TYPE_BOOL, ( profile->male ) ? "1" : "0" ); + g_string_append( attributes, attrib ); + acount++; + + /* update title */ + name = purple_request_fields_get_string( fields, "title" ); + if ( !name ) + profile->title[0] = '\0'; + else + strcpy( profile->title, name ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_TITLE, CP_PROF_TYPE_UTF8, profile->title ); + g_string_append( attributes, attrib ); + acount++; + + /* update firstname */ + name = purple_request_fields_get_string( fields, "firstname" ); + if ( !name ) + profile->firstname[0] = '\0'; + else + strcpy( profile->firstname, name ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_FIRSTNAME, CP_PROF_TYPE_UTF8, profile->firstname ); + g_string_append( attributes, attrib ); + acount++; + + /* update lastname */ + name = purple_request_fields_get_string( fields, "lastname" ); + if ( !name ) + profile->lastname[0] = '\0'; + else + strcpy( profile->lastname, name ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_LASTNAME, CP_PROF_TYPE_UTF8, profile->lastname ); + g_string_append( attributes, attrib ); + acount++; + + /* update email address */ + name = purple_request_fields_get_string( fields, "email" ); + if ( !name ) + profile->email[0] = '\0'; + else + strcpy( profile->email, name ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_EMAIL, CP_PROF_TYPE_UTF8, profile->email ); + g_string_append( attributes, attrib ); + acount++; + + /* update mobile number */ + name = purple_request_fields_get_string( fields, "mobilenumber" ); + if ( !name ) + profile->mobilenr[0] = '\0'; + else + strcpy( profile->mobilenr, name ); + g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_MOBILENR, CP_PROF_TYPE_UTF8, profile->mobilenr ); + g_string_append( attributes, attrib ); + acount++; + + /* send the profile update to MXit */ + mxit_send_extprofile_update( session, session->encpwd, acount, attributes->str ); + g_string_free( attributes, TRUE ); + } + else { + /* show error to user */ + mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Profile Update Error" ), _( err ) ); + } +} + + +/*------------------------------------------------------------------------ + * Display and update the user's profile. + * + * @param action The action object + */ +static void mxit_cb_action_profile( PurplePluginAction* action ) +{ + PurpleConnection* gc = (PurpleConnection*) action->context; + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + struct MXitProfile* profile = session->profile; + + PurpleRequestFields* fields = NULL; + PurpleRequestFieldGroup* group = NULL; + PurpleRequestField* field = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_action_profile\n" ); + + /* ensure that we actually have the user's profile information */ + if ( !profile ) { + /* no profile information yet, so we cannot update */ + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Profile" ), _( "Your profile information is not yet retrieved. Please try again later." ) ); + return; + } + + fields = purple_request_fields_new(); + group = purple_request_field_group_new( NULL ); + purple_request_fields_add_group( fields, group ); + + /* pin */ + field = purple_request_field_string_new( "pin", _( "PIN" ), session->acc->password, FALSE ); + purple_request_field_string_set_masked( field, TRUE ); + purple_request_field_group_add_field( group, field ); + field = purple_request_field_string_new( "pin2", _( "Verify PIN" ), session->acc->password, FALSE ); + purple_request_field_string_set_masked( field, TRUE ); + purple_request_field_group_add_field( group, field ); + + /* display name */ + field = purple_request_field_string_new( "name", _( "Display Name" ), profile->nickname, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* birthday */ + field = purple_request_field_string_new( "bday", _( "Birthday" ), profile->birthday, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* gender */ + field = purple_request_field_choice_new( "male", _( "Gender" ), ( profile->male ) ? 1 : 0 ); + purple_request_field_choice_add( field, _( "Female" ) ); /* 0 */ + purple_request_field_choice_add( field, _( "Male" ) ); /* 1 */ + purple_request_field_group_add_field( group, field ); + + /* hidden */ + field = purple_request_field_bool_new( "hidden", _( "Hide my number" ), profile->hidden ); + purple_request_field_group_add_field( group, field ); + + /* title */ + field = purple_request_field_string_new( "title", _( "Title" ), profile->title, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* first name */ + field = purple_request_field_string_new( "firstname", _( "First Name" ), profile->firstname, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* last name */ + field = purple_request_field_string_new( "lastname", _( "Last Name" ), profile->lastname, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* email */ + field = purple_request_field_string_new( "email", _( "Email" ), profile->email, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* mobile number */ + field = purple_request_field_string_new( "mobilenumber", _( "Mobile Number" ), profile->mobilenr, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* (reference: "libpurple/request.h") */ + purple_request_fields( gc, _( "Profile" ), _( "Update your Profile" ), _( "Here you can update your MXit profile" ), fields, _( "Set" ), + G_CALLBACK( mxit_cb_set_profile ), _( "Cancel" ), NULL, purple_connection_get_account( gc ), NULL, NULL, gc ); +} + + +/*------------------------------------------------------------------------ + * Display the current splash-screen, or a notification pop-up if one is not available. + * + * @param action The action object + */ +static void mxit_cb_action_splash( PurplePluginAction* action ) +{ + PurpleConnection* gc = (PurpleConnection*) action->context; + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + + if ( splash_current( session ) != NULL ) + splash_display( session ); + else + mxit_popup( PURPLE_NOTIFY_MSG_INFO, _( "View Splash" ), _( "There is no splash-screen currently available" ) ); +} + + +/*------------------------------------------------------------------------ + * Display info about the plugin. + * + * @param action The action object + */ +static void mxit_cb_action_about( PurplePluginAction* action ) +{ + char version[256]; + + g_snprintf( version, sizeof( version ), "MXit libPurple Plugin v%s\n" + "MXit Client Protocol v%s\n\n" + "Author:\nPieter Loubser\n\n" + "Contributors:\nAndrew Victor\n\n" + "Testers:\nBraeme Le Roux\n\n", + MXIT_PLUGIN_VERSION, MXIT_CP_RELEASE ); + + mxit_popup( PURPLE_NOTIFY_MSG_INFO, _( "About" ), version ); +} + + +/*------------------------------------------------------------------------ + * Associate actions with the MXit plugin. + * + * @param plugin The MXit protocol plugin + * @param context The connection context (if available) + * @return The list of plugin actions + */ +GList* mxit_actions( PurplePlugin* plugin, gpointer context ) +{ + PurplePluginAction* action = NULL; + GList* m = NULL; + + /* display / change mood */ + action = purple_plugin_action_new( _( "Change Mood..." ), mxit_cb_action_mood ); + m = g_list_append( m, action ); + + /* display / change profile */ + action = purple_plugin_action_new( _( "Change Profile..." ), mxit_cb_action_profile ); + m = g_list_append( m, action ); + + /* display splash-screen */ + action = purple_plugin_action_new( _( "View Splash..." ), mxit_cb_action_splash ); + m = g_list_append( m, action ); + + /* display plugin version */ + action = purple_plugin_action_new( _( "About..." ), mxit_cb_action_about ); + m = g_list_append( m, action ); + + return m; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/actions.h Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,34 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- handle MXit plugin actions -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_ACTIONS_H_ +#define _MXIT_ACTIONS_H_ + + +/* callbacks */ +GList* mxit_actions( PurplePlugin* plugin, gpointer context ); + + +#endif /* _MXIT_ACTIONS_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/aes.c Wed Nov 11 03:07:21 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 <string.h> +#include <memory.h> + +#include "aes.h" + +// AES only supports Nb=4 +#define Nb 4 // number of columns in the state & expanded key + +#define Nk 4 // number of columns in a key +#define Nr 10 // number of rounds in encryption + +static uchar Sbox[256] = { // forward s-box +0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, +0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, +0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, +0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, +0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, +0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, +0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, +0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, +0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, +0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, +0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, +0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, +0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, +0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, +0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, +0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16}; + +static uchar InvSbox[256] = { // inverse s-box +0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, +0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, +0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, +0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, +0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, +0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, +0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, +0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, +0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, +0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, +0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, +0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, +0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, +0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, +0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, +0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d}; + +// combined Xtimes2[Sbox[]] +static uchar Xtime2Sbox[256] = { +0xc6, 0xf8, 0xee, 0xf6, 0xff, 0xd6, 0xde, 0x91, 0x60, 0x02, 0xce, 0x56, 0xe7, 0xb5, 0x4d, 0xec, +0x8f, 0x1f, 0x89, 0xfa, 0xef, 0xb2, 0x8e, 0xfb, 0x41, 0xb3, 0x5f, 0x45, 0x23, 0x53, 0xe4, 0x9b, +0x75, 0xe1, 0x3d, 0x4c, 0x6c, 0x7e, 0xf5, 0x83, 0x68, 0x51, 0xd1, 0xf9, 0xe2, 0xab, 0x62, 0x2a, +0x08, 0x95, 0x46, 0x9d, 0x30, 0x37, 0x0a, 0x2f, 0x0e, 0x24, 0x1b, 0xdf, 0xcd, 0x4e, 0x7f, 0xea, +0x12, 0x1d, 0x58, 0x34, 0x36, 0xdc, 0xb4, 0x5b, 0xa4, 0x76, 0xb7, 0x7d, 0x52, 0xdd, 0x5e, 0x13, +0xa6, 0xb9, 0x00, 0xc1, 0x40, 0xe3, 0x79, 0xb6, 0xd4, 0x8d, 0x67, 0x72, 0x94, 0x98, 0xb0, 0x85, +0xbb, 0xc5, 0x4f, 0xed, 0x86, 0x9a, 0x66, 0x11, 0x8a, 0xe9, 0x04, 0xfe, 0xa0, 0x78, 0x25, 0x4b, +0xa2, 0x5d, 0x80, 0x05, 0x3f, 0x21, 0x70, 0xf1, 0x63, 0x77, 0xaf, 0x42, 0x20, 0xe5, 0xfd, 0xbf, +0x81, 0x18, 0x26, 0xc3, 0xbe, 0x35, 0x88, 0x2e, 0x93, 0x55, 0xfc, 0x7a, 0xc8, 0xba, 0x32, 0xe6, +0xc0, 0x19, 0x9e, 0xa3, 0x44, 0x54, 0x3b, 0x0b, 0x8c, 0xc7, 0x6b, 0x28, 0xa7, 0xbc, 0x16, 0xad, +0xdb, 0x64, 0x74, 0x14, 0x92, 0x0c, 0x48, 0xb8, 0x9f, 0xbd, 0x43, 0xc4, 0x39, 0x31, 0xd3, 0xf2, +0xd5, 0x8b, 0x6e, 0xda, 0x01, 0xb1, 0x9c, 0x49, 0xd8, 0xac, 0xf3, 0xcf, 0xca, 0xf4, 0x47, 0x10, +0x6f, 0xf0, 0x4a, 0x5c, 0x38, 0x57, 0x73, 0x97, 0xcb, 0xa1, 0xe8, 0x3e, 0x96, 0x61, 0x0d, 0x0f, +0xe0, 0x7c, 0x71, 0xcc, 0x90, 0x06, 0xf7, 0x1c, 0xc2, 0x6a, 0xae, 0x69, 0x17, 0x99, 0x3a, 0x27, +0xd9, 0xeb, 0x2b, 0x22, 0xd2, 0xa9, 0x07, 0x33, 0x2d, 0x3c, 0x15, 0xc9, 0x87, 0xaa, 0x50, 0xa5, +0x03, 0x59, 0x09, 0x1a, 0x65, 0xd7, 0x84, 0xd0, 0x82, 0x29, 0x5a, 0x1e, 0x7b, 0xa8, 0x6d, 0x2c +}; + +// combined Xtimes3[Sbox[]] +static uchar Xtime3Sbox[256] = { +0xa5, 0x84, 0x99, 0x8d, 0x0d, 0xbd, 0xb1, 0x54, 0x50, 0x03, 0xa9, 0x7d, 0x19, 0x62, 0xe6, 0x9a, +0x45, 0x9d, 0x40, 0x87, 0x15, 0xeb, 0xc9, 0x0b, 0xec, 0x67, 0xfd, 0xea, 0xbf, 0xf7, 0x96, 0x5b, +0xc2, 0x1c, 0xae, 0x6a, 0x5a, 0x41, 0x02, 0x4f, 0x5c, 0xf4, 0x34, 0x08, 0x93, 0x73, 0x53, 0x3f, +0x0c, 0x52, 0x65, 0x5e, 0x28, 0xa1, 0x0f, 0xb5, 0x09, 0x36, 0x9b, 0x3d, 0x26, 0x69, 0xcd, 0x9f, +0x1b, 0x9e, 0x74, 0x2e, 0x2d, 0xb2, 0xee, 0xfb, 0xf6, 0x4d, 0x61, 0xce, 0x7b, 0x3e, 0x71, 0x97, +0xf5, 0x68, 0x00, 0x2c, 0x60, 0x1f, 0xc8, 0xed, 0xbe, 0x46, 0xd9, 0x4b, 0xde, 0xd4, 0xe8, 0x4a, +0x6b, 0x2a, 0xe5, 0x16, 0xc5, 0xd7, 0x55, 0x94, 0xcf, 0x10, 0x06, 0x81, 0xf0, 0x44, 0xba, 0xe3, +0xf3, 0xfe, 0xc0, 0x8a, 0xad, 0xbc, 0x48, 0x04, 0xdf, 0xc1, 0x75, 0x63, 0x30, 0x1a, 0x0e, 0x6d, +0x4c, 0x14, 0x35, 0x2f, 0xe1, 0xa2, 0xcc, 0x39, 0x57, 0xf2, 0x82, 0x47, 0xac, 0xe7, 0x2b, 0x95, +0xa0, 0x98, 0xd1, 0x7f, 0x66, 0x7e, 0xab, 0x83, 0xca, 0x29, 0xd3, 0x3c, 0x79, 0xe2, 0x1d, 0x76, +0x3b, 0x56, 0x4e, 0x1e, 0xdb, 0x0a, 0x6c, 0xe4, 0x5d, 0x6e, 0xef, 0xa6, 0xa8, 0xa4, 0x37, 0x8b, +0x32, 0x43, 0x59, 0xb7, 0x8c, 0x64, 0xd2, 0xe0, 0xb4, 0xfa, 0x07, 0x25, 0xaf, 0x8e, 0xe9, 0x18, +0xd5, 0x88, 0x6f, 0x72, 0x24, 0xf1, 0xc7, 0x51, 0x23, 0x7c, 0x9c, 0x21, 0xdd, 0xdc, 0x86, 0x85, +0x90, 0x42, 0xc4, 0xaa, 0xd8, 0x05, 0x01, 0x12, 0xa3, 0x5f, 0xf9, 0xd0, 0x91, 0x58, 0x27, 0xb9, +0x38, 0x13, 0xb3, 0x33, 0xbb, 0x70, 0x89, 0xa7, 0xb6, 0x22, 0x92, 0x20, 0x49, 0xff, 0x78, 0x7a, +0x8f, 0xf8, 0x80, 0x17, 0xda, 0x31, 0xc6, 0xb8, 0xc3, 0xb0, 0x77, 0x11, 0xcb, 0xfc, 0xd6, 0x3a +}; + +// modular multiplication tables +// based on: + +// Xtime2[x] = (x & 0x80 ? 0x1b : 0) ^ (x + x) +// Xtime3[x] = x^Xtime2[x]; + +#if 0 +static uchar Xtime2[256] = { +0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, +0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, +0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, +0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, +0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, +0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, +0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, +0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe, +0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05, +0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25, +0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45, +0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65, +0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85, +0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5, +0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5, +0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5}; +#endif + +static uchar Xtime9[256] = { +0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, +0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7, +0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, +0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc, +0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01, +0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91, +0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a, +0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa, +0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b, +0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b, +0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0, +0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30, +0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed, +0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d, +0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6, +0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46}; + +static uchar XtimeB[256] = { +0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69, +0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9, +0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12, +0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2, +0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f, +0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f, +0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4, +0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54, +0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e, +0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e, +0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5, +0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55, +0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68, +0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8, +0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13, +0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3}; + +static uchar XtimeD[256] = { +0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b, +0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b, +0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0, +0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20, +0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26, +0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6, +0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d, +0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d, +0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91, +0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41, +0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a, +0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa, +0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc, +0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c, +0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47, +0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97}; + +static uchar XtimeE[256] = { +0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a, +0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba, +0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81, +0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61, +0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7, +0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17, +0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c, +0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc, +0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b, +0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb, +0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0, +0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20, +0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6, +0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56, +0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d, +0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d}; + +// exchanges columns in each of 4 rows +// row0 - unchanged, row1- shifted left 1, +// row2 - shifted left 2 and row3 - shifted left 3 +static void ShiftRows (uchar *state) +{ +uchar tmp; + + // just substitute row 0 + state[0] = Sbox[state[0]], state[4] = Sbox[state[4]]; + state[8] = Sbox[state[8]], state[12] = Sbox[state[12]]; + + // rotate row 1 + tmp = Sbox[state[1]], state[1] = Sbox[state[5]]; + state[5] = Sbox[state[9]], state[9] = Sbox[state[13]], state[13] = tmp; + + // rotate row 2 + tmp = Sbox[state[2]], state[2] = Sbox[state[10]], state[10] = tmp; + tmp = Sbox[state[6]], state[6] = Sbox[state[14]], state[14] = tmp; + + // rotate row 3 + tmp = Sbox[state[15]], state[15] = Sbox[state[11]]; + state[11] = Sbox[state[7]], state[7] = Sbox[state[3]], state[3] = tmp; +} + +// restores columns in each of 4 rows +// row0 - unchanged, row1- shifted right 1, +// row2 - shifted right 2 and row3 - shifted right 3 +static void InvShiftRows (uchar *state) +{ +uchar tmp; + + // restore row 0 + state[0] = InvSbox[state[0]], state[4] = InvSbox[state[4]]; + state[8] = InvSbox[state[8]], state[12] = InvSbox[state[12]]; + + // restore row 1 + tmp = InvSbox[state[13]], state[13] = InvSbox[state[9]]; + state[9] = InvSbox[state[5]], state[5] = InvSbox[state[1]], state[1] = tmp; + + // restore row 2 + tmp = InvSbox[state[2]], state[2] = InvSbox[state[10]], state[10] = tmp; + tmp = InvSbox[state[6]], state[6] = InvSbox[state[14]], state[14] = tmp; + + // restore row 3 + tmp = InvSbox[state[3]], state[3] = InvSbox[state[7]]; + state[7] = InvSbox[state[11]], state[11] = InvSbox[state[15]], state[15] = tmp; +} + +// recombine and mix each row in a column +static void MixSubColumns (uchar *state) +{ +uchar tmp[4 * Nb]; + + // mixing column 0 + tmp[0] = Xtime2Sbox[state[0]] ^ Xtime3Sbox[state[5]] ^ Sbox[state[10]] ^ Sbox[state[15]]; + tmp[1] = Sbox[state[0]] ^ Xtime2Sbox[state[5]] ^ Xtime3Sbox[state[10]] ^ Sbox[state[15]]; + tmp[2] = Sbox[state[0]] ^ Sbox[state[5]] ^ Xtime2Sbox[state[10]] ^ Xtime3Sbox[state[15]]; + tmp[3] = Xtime3Sbox[state[0]] ^ Sbox[state[5]] ^ Sbox[state[10]] ^ Xtime2Sbox[state[15]]; + + // mixing column 1 + tmp[4] = Xtime2Sbox[state[4]] ^ Xtime3Sbox[state[9]] ^ Sbox[state[14]] ^ Sbox[state[3]]; + tmp[5] = Sbox[state[4]] ^ Xtime2Sbox[state[9]] ^ Xtime3Sbox[state[14]] ^ Sbox[state[3]]; + tmp[6] = Sbox[state[4]] ^ Sbox[state[9]] ^ Xtime2Sbox[state[14]] ^ Xtime3Sbox[state[3]]; + tmp[7] = Xtime3Sbox[state[4]] ^ Sbox[state[9]] ^ Sbox[state[14]] ^ Xtime2Sbox[state[3]]; + + // mixing column 2 + tmp[8] = Xtime2Sbox[state[8]] ^ Xtime3Sbox[state[13]] ^ Sbox[state[2]] ^ Sbox[state[7]]; + tmp[9] = Sbox[state[8]] ^ Xtime2Sbox[state[13]] ^ Xtime3Sbox[state[2]] ^ Sbox[state[7]]; + tmp[10] = Sbox[state[8]] ^ Sbox[state[13]] ^ Xtime2Sbox[state[2]] ^ Xtime3Sbox[state[7]]; + tmp[11] = Xtime3Sbox[state[8]] ^ Sbox[state[13]] ^ Sbox[state[2]] ^ Xtime2Sbox[state[7]]; + + // mixing column 3 + tmp[12] = Xtime2Sbox[state[12]] ^ Xtime3Sbox[state[1]] ^ Sbox[state[6]] ^ Sbox[state[11]]; + tmp[13] = Sbox[state[12]] ^ Xtime2Sbox[state[1]] ^ Xtime3Sbox[state[6]] ^ Sbox[state[11]]; + tmp[14] = Sbox[state[12]] ^ Sbox[state[1]] ^ Xtime2Sbox[state[6]] ^ Xtime3Sbox[state[11]]; + tmp[15] = Xtime3Sbox[state[12]] ^ Sbox[state[1]] ^ Sbox[state[6]] ^ Xtime2Sbox[state[11]]; + + memcpy (state, tmp, sizeof(tmp)); +} + +// restore and un-mix each row in a column +static void InvMixSubColumns (uchar *state) +{ +uchar tmp[4 * Nb]; +int i; + + // restore column 0 + tmp[0] = XtimeE[state[0]] ^ XtimeB[state[1]] ^ XtimeD[state[2]] ^ Xtime9[state[3]]; + tmp[5] = Xtime9[state[0]] ^ XtimeE[state[1]] ^ XtimeB[state[2]] ^ XtimeD[state[3]]; + tmp[10] = XtimeD[state[0]] ^ Xtime9[state[1]] ^ XtimeE[state[2]] ^ XtimeB[state[3]]; + tmp[15] = XtimeB[state[0]] ^ XtimeD[state[1]] ^ Xtime9[state[2]] ^ XtimeE[state[3]]; + + // restore column 1 + tmp[4] = XtimeE[state[4]] ^ XtimeB[state[5]] ^ XtimeD[state[6]] ^ Xtime9[state[7]]; + tmp[9] = Xtime9[state[4]] ^ XtimeE[state[5]] ^ XtimeB[state[6]] ^ XtimeD[state[7]]; + tmp[14] = XtimeD[state[4]] ^ Xtime9[state[5]] ^ XtimeE[state[6]] ^ XtimeB[state[7]]; + tmp[3] = XtimeB[state[4]] ^ XtimeD[state[5]] ^ Xtime9[state[6]] ^ XtimeE[state[7]]; + + // restore column 2 + tmp[8] = XtimeE[state[8]] ^ XtimeB[state[9]] ^ XtimeD[state[10]] ^ Xtime9[state[11]]; + tmp[13] = Xtime9[state[8]] ^ XtimeE[state[9]] ^ XtimeB[state[10]] ^ XtimeD[state[11]]; + tmp[2] = XtimeD[state[8]] ^ Xtime9[state[9]] ^ XtimeE[state[10]] ^ XtimeB[state[11]]; + tmp[7] = XtimeB[state[8]] ^ XtimeD[state[9]] ^ Xtime9[state[10]] ^ XtimeE[state[11]]; + + // restore column 3 + tmp[12] = XtimeE[state[12]] ^ XtimeB[state[13]] ^ XtimeD[state[14]] ^ Xtime9[state[15]]; + tmp[1] = Xtime9[state[12]] ^ XtimeE[state[13]] ^ XtimeB[state[14]] ^ XtimeD[state[15]]; + tmp[6] = XtimeD[state[12]] ^ Xtime9[state[13]] ^ XtimeE[state[14]] ^ XtimeB[state[15]]; + tmp[11] = XtimeB[state[12]] ^ XtimeD[state[13]] ^ Xtime9[state[14]] ^ XtimeE[state[15]]; + + for( i=0; i < 4 * Nb; i++ ) + state[i] = InvSbox[tmp[i]]; +} + +// encrypt/decrypt columns of the key +// n.b. you can replace this with +// byte-wise xor if you wish. + +static void AddRoundKey (unsigned *state, unsigned *key) +{ +int idx; + + for( idx = 0; idx < 4; idx++ ) + state[idx] ^= key[idx]; +} + +static uchar Rcon[11] = { +0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36}; + +// produce Nb bytes for each round +void ExpandKey (uchar *key, uchar *expkey) +{ +uchar tmp0, tmp1, tmp2, tmp3, tmp4; +unsigned idx; + + memcpy (expkey, key, Nk * 4); + + for( idx = Nk; idx < Nb * (Nr + 1); idx++ ) { + tmp0 = expkey[4*idx - 4]; + tmp1 = expkey[4*idx - 3]; + tmp2 = expkey[4*idx - 2]; + tmp3 = expkey[4*idx - 1]; + if( !(idx % Nk) ) { + tmp4 = tmp3; + tmp3 = Sbox[tmp0]; + tmp0 = Sbox[tmp1] ^ Rcon[idx/Nk]; + tmp1 = Sbox[tmp2]; + tmp2 = Sbox[tmp4]; + } else if( Nk > 6 && idx % Nk == 4 ) { + tmp0 = Sbox[tmp0]; + tmp1 = Sbox[tmp1]; + tmp2 = Sbox[tmp2]; + tmp3 = Sbox[tmp3]; + } + + expkey[4*idx+0] = expkey[4*idx - 4*Nk + 0] ^ tmp0; + expkey[4*idx+1] = expkey[4*idx - 4*Nk + 1] ^ tmp1; + expkey[4*idx+2] = expkey[4*idx - 4*Nk + 2] ^ tmp2; + expkey[4*idx+3] = expkey[4*idx - 4*Nk + 3] ^ tmp3; + } +} + +// encrypt one 128 bit block +void Encrypt (uchar *in, uchar *expkey, uchar *out) +{ +uchar state[Nb * 4]; +unsigned round; + + memcpy (state, in, Nb * 4); + AddRoundKey ((unsigned *)state, (unsigned *)expkey); + + for( round = 1; round < Nr + 1; round++ ) { + if( round < Nr ) + MixSubColumns (state); + else + ShiftRows (state); + + AddRoundKey ((unsigned *)state, (unsigned *)expkey + round * Nb); + } + + memcpy (out, state, sizeof(state)); +} + +void Decrypt (uchar *in, uchar *expkey, uchar *out) +{ +uchar state[Nb * 4]; +unsigned round; + + memcpy (state, in, sizeof(state)); + + AddRoundKey ((unsigned *)state, (unsigned *)expkey + Nr * Nb); + InvShiftRows(state); + + for( round = Nr; round--; ) + { + AddRoundKey ((unsigned *)state, (unsigned *)expkey + round * Nb); + if( round ) + InvMixSubColumns (state); + } + + memcpy (out, state, sizeof(state)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/aes.h Wed Nov 11 03:07:21 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 */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/chunk.c Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,659 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- handle chunked data (multimedia messages) -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#include "purple.h" +#include "protocol.h" +#include "mxit.h" +#include "chunk.h" +#include "filexfer.h" + + +/*======================================================================================================================== + * Data-Type encoding + */ + +#if 0 +#include <byteswap.h> +#if (__BYTE_ORDER == __BIG_ENDIAN) +#define SWAP_64(x) (x) +#else +#define SWAP_64(x) bswap_64(x) +#endif +#endif + +/*------------------------------------------------------------------------ + * Encode a single byte in the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The byte + * @return The number of bytes added. + */ +static int add_int8( char* chunkdata, char value ) +{ + *chunkdata = value; + + return sizeof( char ); +} + +/*------------------------------------------------------------------------ + * Encode a 16-bit value in the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The 16-bit value + * @return The number of bytes added. + */ +static int add_int16( char* chunkdata, short value ) +{ + value = htons( value ); /* network byte-order */ + memcpy( chunkdata, &value, sizeof( short ) ); + + return sizeof( short ); +} + +/*------------------------------------------------------------------------ + * Encode a 32-bit value in the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The 32-bit value + * @return The number of bytes added. + */ +static int add_int32( char* chunkdata, int value ) +{ + value = htonl( value ); /* network byte-order */ + memcpy( chunkdata, &value, sizeof( int ) ); + + return sizeof( int ); +} + +#if 0 +/*------------------------------------------------------------------------ + * Encode a 64-bit value in the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The 64-bit value + * @return The number of bytes added. + */ +static int add_int64( char* chunkdata, int64_t value ) +{ + value = SWAP_64( value ); /* network byte-order */ + memcpy( chunkdata, &value, sizeof( int64_t ) ); + + return sizeof( int64_t ); +} +#endif + +/*------------------------------------------------------------------------ + * Encode a block of data in the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param data The data to add + * @param datalen The length of the data to add + * @return The number of bytes added. + */ +static int add_data( char* chunkdata, const char* data, int datalen ) +{ + memcpy( chunkdata, data, datalen ); + + return datalen; +} + +/*------------------------------------------------------------------------ + * Encode a string as UTF-8 in the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param str The string to encode + * @return The number of bytes in the string + */ +static int add_utf8_string( char* chunkdata, const char* str ) +{ + int pos = 0; + size_t len = strlen( str ); + + /* utf8 string length [2 bytes] */ + pos += add_int16( &chunkdata[pos], len ); + + /* utf8 string */ + pos += add_data( &chunkdata[pos], str, len ); + + return pos; +} + + +/*======================================================================================================================== + * Data-Type decoding + */ + +/*------------------------------------------------------------------------ + * Extract a single byte from the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The byte + * @return The number of bytes extracted. + */ +static int get_int8( const char* chunkdata, char* value ) +{ + *value = *chunkdata; + + return sizeof( char ); +} + +/*------------------------------------------------------------------------ + * Extract a 16-bit value from the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The 16-bit value + * @return The number of bytes extracted + */ +static int get_int16( const char* chunkdata, short* value ) +{ + *value = ntohs( *( (const short*) chunkdata ) ); /* host byte-order */ + + return sizeof( short ); +} + +/*------------------------------------------------------------------------ + * Extract a 32-bit value from the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The 32-bit value + * @return The number of bytes extracted + */ +static int get_int32( const char* chunkdata, int* value ) +{ + *value = ntohl( *( (const int*) chunkdata ) ); /* host byte-order */ + + return sizeof( int ); +} + +#if 0 +/*------------------------------------------------------------------------ + * Extract a 64-bit value from the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param value The 64-bit value + * @return The number of bytes extracted + */ +static int get_int64( const char* chunkdata, int64_t* value ) +{ + *value = SWAP_64( *( (const int64_t*) chunkdata ) ); /* host byte-order */ + + return sizeof( int64_t ); +} +#endif + +/*------------------------------------------------------------------------ + * Copy a block of data from the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param dest Where to store the extract data + * @param datalen The length of the data to extract + * @return The number of bytes extracted + */ +static int get_data( const char* chunkdata, char* dest, int datalen ) +{ + memcpy( dest, chunkdata, datalen ); + + return datalen; +} + +/*------------------------------------------------------------------------ + * Extract a UTF-8 encoded string from the chunked data. + * + * @param chunkdata The chunked-data buffer + * @param str A pointer to extracted string. Must be g_free()'d. + * @return The number of bytes consumed + */ +static int get_utf8_string( const char* chunkdata, char* str, int maxstrlen ) +{ + int pos = 0; + short len; + int skip = 0; + + /* string length [2 bytes] */ + pos += get_int16( &chunkdata[pos], &len ); + + if ( len > maxstrlen ) { + /* possible buffer overflow */ + purple_debug_error( MXIT_PLUGIN_ID, "Buffer overflow detected (get_utf8_string)\n" ); + skip = len - maxstrlen; + len = maxstrlen; + } + + /* string data */ + pos += get_data( &chunkdata[pos], str, len ); + str[len] = '\0'; /* terminate string */ + + return pos + skip; +} + + +/*======================================================================================================================== + * Chunked Data encoding + */ + +/*------------------------------------------------------------------------ + * Encode a "reject file" chunk. (Chunk type 7) + * + * @param chunkdata Chunked-data buffer + * @param fileid A unique ID that identifies this file + * @return The number of bytes encoded in the buffer + */ +int mxit_chunk_create_reject( char* chunkdata, const char* fileid ) +{ + int pos = 0; + + /* file id [8 bytes] */ + pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN ); + + /* rejection reason [1 byte] */ + pos += add_int8( &chunkdata[pos], REJECT_BY_USER ); + + /* rejection description [UTF-8 (optional)] */ + pos += add_utf8_string( &chunkdata[pos], "" ); + + return pos; +} + + +/*------------------------------------------------------------------------ + * Encode a "get file" request chunk. (Chunk type 8) + * + * @param chunkdata Chunked-data buffer + * @param fileid A unique ID that identifies this file + * @param filesize The number of bytes to retrieve + * @param offset The start offset in the file + * @return The number of bytes encoded in the buffer + */ +int mxit_chunk_create_get( char* chunkdata, const char* fileid, int filesize, int offset ) +{ + int pos = 0; + + /* file id [8 bytes] */ + pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN ); + + /* offset [4 bytes] */ + pos += add_int32( &chunkdata[pos], offset ); + + /* length [4 bytes] */ + pos += add_int32( &chunkdata[pos], filesize ); + + return pos; +} + + +/*------------------------------------------------------------------------ + * Encode a "received file" chunk. (Chunk type 9) + * + * @param chunkdata Chunked-data buffer + * @param fileid A unique ID that identifies this file + * @param status The status of the file transfer (see chunk.h) + * @return The number of bytes encoded in the buffer + */ +int mxit_chunk_create_received( char* chunkdata, const char* fileid, unsigned char status ) +{ + int pos = 0; + + /* file id [8 bytes] */ + pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN ); + + /* status [1 byte] */ + pos += add_int8( &chunkdata[pos], status ); + + return pos; +} + + +/*------------------------------------------------------------------------ + * Encode a "send file direct" chunk. (Chunk type 10) + * + * @param chunkdata Chunked-data buffer + * @param username The username of the recipient + * @param filename The name of the file being sent + * @param data The file contents + * @param datalen The size of the file contents + * @return The number of bytes encoded in the buffer + */ +int mxit_chunk_create_senddirect( char* chunkdata, const char* username, const char* filename, const unsigned char* data, int datalen ) +{ + int pos = 0; + const char* mime = NULL; + + /* data length [4 bytes] */ + pos += add_int32( &chunkdata[pos], datalen ); + + /* number of username(s) [2 bytes] */ + pos += add_int16( &chunkdata[pos], 1 ); + + /* username(s) [UTF-8] */ + pos += add_utf8_string( &chunkdata[pos], username ); + + /* filename [UTF-8] */ + pos += add_utf8_string( &chunkdata[pos], filename ); + + /* file mime type [UTF-8] */ + mime = file_mime_type( filename, (const char*) data, datalen ); + pos += add_utf8_string( &chunkdata[pos], mime ); + + /* human readable description [UTF-8 (optional)] */ + pos += add_utf8_string( &chunkdata[pos], "" ); + + /* crc [4 bytes] (0 = optional) */ + pos += add_int32( &chunkdata[pos], 0 ); + + /* the actual file data */ + pos += add_data( &chunkdata[pos], (const char *) data, datalen ); + + return pos; +} + + +/*------------------------------------------------------------------------ + * Encode a "set avatar" chunk. (Chunk type 13) + * + * @param chunkdata Chunked-data buffer + * @param data The avatar data + * @param datalen The size of the avatar data + * @return The number of bytes encoded in the buffer + */ +int mxit_chunk_create_set_avatar( char* chunkdata, const unsigned char* data, int datalen ) +{ + const char fileid[MXIT_CHUNK_FILEID_LEN]; + int pos = 0; + + /* id [8 bytes] */ + memset( &fileid, 0, sizeof( fileid ) ); /* set to 0 for file upload */ + pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN ); + + /* size [4 bytes] */ + pos += add_int32( &chunkdata[pos], datalen ); + + /* crc [4 bytes] (0 = optional) */ + pos += add_int32( &chunkdata[pos], 0 ); + + /* the actual file data */ + pos += add_data( &chunkdata[pos], (const char *) data, datalen ); + + return pos; +} + + +/*------------------------------------------------------------------------ + * Encode a "get avatar" chunk. (Chunk type 14) + * + * @param chunkdata Chunked-data buffer + * @param mxitId The username who's avatar to download + * @param avatarId The Id of the avatar image (as string) + * @param imgsize The resolution of the avatar image + * @return The number of bytes encoded in the buffer + */ +int mxit_chunk_create_get_avatar( char* chunkdata, const char* mxitId, const char* avatarId, unsigned int imgsize ) +{ + int pos = 0; + + /* number of avatars [4 bytes] */ + pos += add_int32( &chunkdata[pos], 1 ); + + /* username [UTF-8] */ + pos += add_utf8_string( &chunkdata[pos], mxitId ); + + /* avatar id [UTF-8] */ + pos += add_utf8_string( &chunkdata[pos], avatarId ); + + /* avatar format [UTF-8] */ + pos += add_utf8_string( &chunkdata[pos], MXIT_AVATAR_TYPE ); + + /* avatar bit depth [1 byte] */ + pos += add_int8( &chunkdata[pos], MXIT_AVATAR_BITDEPT ); + + /* number of sizes [2 bytes] */ + pos += add_int16( &chunkdata[pos], 1 ); + + /* image size [4 bytes] */ + pos += add_int32( &chunkdata[pos], imgsize ); + + return pos; +} + + +/*======================================================================================================================== + * Chunked Data decoding + */ + +/*------------------------------------------------------------------------ + * Parse a received "offer file" chunk. (Chunk 6) + * + * @param chunkdata Chunked data buffer + * @param datalen The length of the chunked data + * @param offer Decoded offerfile information + */ +void mxit_chunk_parse_offer( char* chunkdata, int datalen, struct offerfile_chunk* offer ) +{ + int pos = 0; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_offer (%i bytes)\n", datalen ); + + /* id [8 bytes] */ + pos += get_data( &chunkdata[pos], offer->fileid, 8); + + /* from username [UTF-8] */ + pos += get_utf8_string( &chunkdata[pos], offer->username, sizeof( offer->username ) ); + mxit_strip_domain( offer->username ); + + /* file size [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(offer->filesize) ); + + /* filename [UTF-8] */ + pos += get_utf8_string( &chunkdata[pos], offer->filename, sizeof( offer->filename) ); + + /* mime type [UTF-8] */ + /* not used by libPurple */ + + /* timestamp [8 bytes] */ + /* not used by libPurple */ + + /* file description [UTF-8] */ + /* not used by libPurple */ + + /* file alternative [UTF-8] */ + /* not used by libPurple */ + + /* flags [4 bytes] */ + /* not used by libPurple */ +} + + +/*------------------------------------------------------------------------ + * Parse a received "get file" response chunk. (Chunk 8) + * + * @param chunkdata Chunked data buffer + * @param datalen The length of the chunked data + * @param offer Decoded getfile information + */ +void mxit_chunk_parse_get( char* chunkdata, int datalen, struct getfile_chunk* getfile ) +{ + int pos = 0; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_file (%i bytes)\n", datalen ); + + /* id [8 bytes] */ + pos += get_data( &chunkdata[pos], getfile->fileid, 8 ); + + /* offset [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(getfile->offset) ); + + /* file length [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(getfile->length) ); + + /* crc [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(getfile->crc) ); + + /* file data */ + getfile->data = &chunkdata[pos]; +} + + +/*------------------------------------------------------------------------ + * Parse a received splash screen chunk. (Chunk 2) + * + * @param chunkdata Chunked data buffer + * @param datalen The length of the chunked data + * @param splash Decoded splash image information + */ +static void mxit_chunk_parse_splash( char* chunkdata, int datalen, struct splash_chunk* splash ) +{ + int pos = 0; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_splash (%i bytes)\n", datalen ); + + /* anchor [1 byte] */ + pos += get_int8( &chunkdata[pos], &(splash->anchor) ); + + /* time to show [1 byte] */ + pos += get_int8( &chunkdata[pos], &(splash->showtime) ); + + /* background color [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(splash->bgcolor) ); + + /* file data */ + splash->data = &chunkdata[pos]; + + /* data length */ + splash->datalen = datalen - pos; +} + + +/*------------------------------------------------------------------------ + * Parse a received "custom resource" chunk. (Chunk 1) + * + * @param chunkdata Chunked data buffer + * @param datalen The length of the chunked data + * @param offer Decoded custom resource + */ +void mxit_chunk_parse_cr( char* chunkdata, int datalen, struct cr_chunk* cr ) +{ + int pos = 0; + int chunklen = 0; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_cr (%i bytes)\n", datalen ); + + /* id [UTF-8] */ + pos += get_utf8_string( &chunkdata[pos], cr->id, sizeof( cr->id ) ); + + /* handle [UTF-8] */ + pos += get_utf8_string( &chunkdata[pos], cr->handle, sizeof( cr->handle ) ); + + /* operation [1 byte] */ + pos += get_int8( &chunkdata[pos], &(cr->operation) ); + + /* chunk size [4 bytes] */ + pos += get_int32( &chunkdata[pos], &chunklen ); + + /* parse the resource chunks */ + while ( chunklen > 0 ) { + struct raw_chunk* chunkhdr = ( struct raw_chunk * ) &chunkdata[pos]; + chunkhdr->length = ntohl( chunkhdr->length ); /* host byte-order */ + + /* start of chunk data */ + pos += sizeof( struct raw_chunk ); + + switch ( chunkhdr->type ) { + case CP_CHUNK_SPLASH : /* splash image */ + { + struct splash_chunk* splash = g_new0( struct splash_chunk, 1 ); + + mxit_chunk_parse_splash( &chunkdata[pos], chunkhdr->length, splash ); + + cr->resources = g_list_append( cr->resources, splash ); + break; + } + case CP_CHUNK_CLICK : /* splash click */ + { + struct splash_click_chunk* click = g_new0( struct splash_click_chunk, 1 ); + + cr->resources = g_list_append( cr->resources, click ); + break; + } + default: + purple_debug_info( MXIT_PLUGIN_ID, "Unsupported custom resource chunk received (%i)\n", chunkhdr->type ); + } + + /* skip over data to next resource chunk */ + pos += chunkhdr->length; + chunklen -= ( sizeof( struct raw_chunk ) + chunkhdr->length ); + } +} + + +/*------------------------------------------------------------------------ + * Parse a received "get avatar" response chunk. (Chunk 14) + * + * @param chunkdata Chunked data buffer + * @param datalen The length of the chunked data + * @param avatar Decoded avatar information + */ +void mxit_chunk_parse_get_avatar( char* chunkdata, int datalen, struct getavatar_chunk* avatar ) +{ + int pos = 0; + int numfiles = 0; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_get_avatar (%i bytes)\n", datalen ); + + /* number of files [4 bytes] */ + pos += get_int32( &chunkdata[pos], &numfiles ); + + if ( numfiles < 1 ) /* no data */ + return; + + /* mxitId [UTF-8 string] */ + pos += get_utf8_string( &chunkdata[pos], avatar->mxitid, sizeof( avatar->mxitid ) ); + + /* avatar id [UTF-8 string] */ + pos += get_utf8_string( &chunkdata[pos], avatar->avatarid, sizeof( avatar->avatarid ) ); + + /* format [UTF-8 string] */ + pos += get_utf8_string( &chunkdata[pos], avatar->format, sizeof( avatar->format ) ); + + /* bit depth [1 byte] */ + pos += get_int8( &chunkdata[pos], &(avatar->bitdepth) ); + + /* crc [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(avatar->crc) ); + + /* width [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(avatar->width) ); + + /* height [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(avatar->height) ); + + /* file length [4 bytes] */ + pos += get_int32( &chunkdata[pos], &(avatar->length) ); + + /* file data */ + avatar->data = &chunkdata[pos]; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/chunk.h Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,140 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- handle chunked data (multimedia messages) -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_CHUNK_H_ +#define _MXIT_CHUNK_H_ + + +#include "roster.h" + + +#define MXIT_CHUNK_FILEID_LEN 8 /* bytes */ + +/* Multimedia chunk types */ +#define CP_CHUNK_NONE 0x00 /* (0) no chunk */ +#define CP_CHUNK_CUSTOM 0x01 /* (1) custom resource */ +#define CP_CHUNK_SPLASH 0x02 /* (2) splash image */ +#define CP_CHUNK_CLICK 0x03 /* (3) splash click through */ +#define CP_CHUNK_OFFER 0x06 /* (6) offer file */ +#define CP_CHUNK_REJECT 0x07 /* (7) reject file */ +#define CP_CHUNK_GET 0x08 /* (8) get file */ +#define CP_CHUNK_RECIEVED 0x09 /* (9) received file */ +#define CP_CHUNK_DIRECT_SND 0x0A /* (10) send file direct */ +#define CP_CHUNK_DIRECT_FWD 0x0B /* (11) forward file direct */ +#define CP_CHUNK_SKIN 0x0C /* (12) MXit client skin */ +#define CP_CHUNK_SET_AVATAR 0x0D /* (13) set avatar */ +#define CP_CHUNK_GET_AVATAR 0x0E /* (14) get avatar */ +#define CP_CHUNK_END 0x7E /* (126) end */ +#define CP_CHUNK_EXT 0x7F /* (127) extended type */ + + +/* Custom Resource operations */ +#define CR_OP_UPDATE 0 +#define CR_OP_REMOVE 1 + +/* File Received status */ +#define RECV_STATUS_SUCCESS 0 +#define RECV_STATUS_PARSE_FAIL 1 +#define RECV_STATUS_CANNOT_OPEN 8 +#define RECV_STATUS_BAD_CRC 9 +#define RECV_STATUS_BAD_ID 10 + +/* File Reject status */ +#define REJECT_BY_USER 1 +#define REJECT_FILETYPE 2 +#define REJECT_NO_RESOURCES 3 +#define REJECT_BAD_RECIPIENT 4 + +/* + * a Chunk header + */ +struct raw_chunk { + guint8 type; + guint32 length; + gchar data[0]; +} __attribute__ ((packed)); + +struct offerfile_chunk { + char fileid[MXIT_CHUNK_FILEID_LEN]; + char username[MXIT_CP_MAX_JID_LEN + 1]; + int filesize; + char filename[FILENAME_MAX]; +}; + +struct getfile_chunk { + char fileid[MXIT_CHUNK_FILEID_LEN]; + int offset; + int length; + int crc; + char* data; +}; + +struct cr_chunk { + char id[64]; + char handle[64]; + char operation; + GList* resources; +}; + +struct splash_chunk { + char anchor; + char showtime; + int bgcolor; + char* data; + int datalen; +}; + +struct splash_click_chunk { + char reserved[1]; +}; + +struct getavatar_chunk { + char mxitid[50]; + char avatarid[64]; + char format[16]; + char bitdepth; + int crc; + int width; + int height; + int length; + char* data; +}; + +/* Encode chunk */ +int mxit_chunk_create_senddirect( char* chunkdata, const char* username, const char* filename, const unsigned char* data, int datalen ); +int mxit_chunk_create_reject( char* chunkdata, const char* fileid ); +int mxit_chunk_create_get( char* chunkdata, const char* fileid, int filesize, int offset ); +int mxit_chunk_create_received( char* chunkdata, const char* fileid, unsigned char status ); +int mxit_chunk_create_set_avatar( char* chunkdata, const unsigned char* data, int datalen ); +int mxit_chunk_create_get_avatar( char* chunkdata, const char* mxitId, const char* avatarId, unsigned int imgsize ); + +/* Decode chunk */ +void mxit_chunk_parse_offer( char* chunkdata, int datalen, struct offerfile_chunk* offer ); +void mxit_chunk_parse_get( char* chunkdata, int datalen, struct getfile_chunk* getfile ); +void mxit_chunk_parse_cr( char* chunkdata, int datalen, struct cr_chunk* cr ); +void mxit_chunk_parse_get_avatar( char* chunkdata, int datalen, struct getavatar_chunk* avatar ); + +#endif /* _MXIT_CHUNK_H_ */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/cipher.c Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,111 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user password encryption -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#include "purple.h" + +#include "mxit.h" +#include "cipher.h" +#include "aes.h" + + +/* password encryption */ +#define INITIAL_KEY "6170383452343567" +#define SECRET_HEADER "<mxit/>" + + +/*------------------------------------------------------------------------ + * Pad the secret data using ISO10126 Padding. + * + * @param secret The data to pad (caller must ensure buffer has enough space for padding) + * @return The total number of 128-bit blocks used + */ +static int pad_secret_data( char* secret ) +{ + int blocks = 0; + int passlen; + int padding; + + passlen = strlen( secret ); + blocks = ( passlen / 16 ) + 1; + padding = ( blocks * 16 ) - passlen; + secret[passlen] = 0x50; + secret[(blocks * 16) - 1] = padding; + + return blocks; +} + + +/*------------------------------------------------------------------------ + * Encrypt the user's cleartext password using the AES 128-bit (ECB) + * encryption algorithm. + * + * @param session The MXit session object + * @return The encrypted & encoded password. Must be g_free'd when no longer needed. + */ +char* mxit_encrypt_password( struct MXitSession* session ) +{ + char key[64]; + char exkey[512]; + char pass[64]; + char encrypted[64]; + char* base64; + int blocks; + int size; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_encrypt_password\n" ); + + memset( encrypted, 0x00, sizeof( encrypted ) ); + memset( exkey, 0x00, sizeof( exkey ) ); + memset( pass, 0x58, sizeof( pass ) ); + pass[sizeof( pass ) - 1] = '\0'; + + /* build the custom AES encryption key */ + strcpy( key, INITIAL_KEY ); + memcpy( key, session->clientkey, strlen( session->clientkey ) ); + ExpandKey( (unsigned char*) key, (unsigned char*) exkey ); + + /* build the custom data to be encrypted */ + strcpy( pass, SECRET_HEADER ); + strcat( pass, session->acc->password ); + + /* pad the secret data */ + blocks = pad_secret_data( pass ); + size = blocks * 16; + + /* now encrypt the password. we encrypt each block separately (ECB mode) */ + for ( i = 0; i < size; i += 16 ) + Encrypt( (unsigned char*) pass + i, (unsigned char*) exkey, (unsigned char*) encrypted + i ); + + /* now base64 encode the encrypted password */ + base64 = purple_base64_encode( (unsigned char*) encrypted, size ); + + return base64; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/cipher.h Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,36 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user password encryption -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_CIPHER_H_ +#define _MXIT_CIPHER_H_ + + +struct MXitSession; + + +char* mxit_encrypt_password( struct MXitSession* session ); + + +#endif /* _MXIT_CIPHER_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/filexfer.c Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,454 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- file transfers (sending and receiving) -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#include "purple.h" +#include "protocol.h" +#include "mxit.h" +#include "chunk.h" +#include "filexfer.h" + + +#define MIME_TYPE_OCTETSTREAM "application/octet-stream" + + +/* supported file mime types */ +static struct mime_type { + const char* magic; + const short magic_len; + const char* mime; +} const mime_types[] = { + /* magic length mime */ + /* images */ { "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8, "image/png" }, /* image png */ + { "\xFF\xD8", 2, "image/jpeg" }, /* image jpeg */ + { "\x3C\x3F\x78\x6D\x6C", 5, "image/svg+xml" }, /* image SVGansi */ + { "\xEF\xBB\xBF", 3, "image/svg+xml" }, /* image SVGutf */ + { "\xEF\xBB\xBF", 3, "image/svg+xml" }, /* image SVGZ */ + /* mxit */ { "\x4d\x58\x4d", 3, "application/mxit-msgs" }, /* mxit message */ + { "\x4d\x58\x44\x01", 4, "application/mxit-mood" }, /* mxit mood */ + { "\x4d\x58\x45\x01", 4, "application/mxit-emo" }, /* mxit emoticon */ + { "\x4d\x58\x46\x01", 4, "application/mxit-emof" }, /* mxit emoticon frame */ + { "\x4d\x58\x53\x01", 4, "application/mxit-skin" }, /* mxit skin */ + /* audio */ { "\x4d\x54\x68\x64", 4, "audio/midi" }, /* audio midi */ + { "\x52\x49\x46\x46", 4, "audio/wav" }, /* audio wav */ + { "\xFF\xF1", 2, "audio/aac" }, /* audio aac1 */ + { "\xFF\xF9", 2, "audio/aac" }, /* audio aac2 */ + { "\xFF", 1, "audio/mp3" }, /* audio mp3 */ + { "\x23\x21\x41\x4D\x52\x0A", 6, "audio/amr" }, /* audio AMR */ + { "\x23\x21\x41\x4D\x52\x2D\x57\x42", 8, "audio/amr-wb" }, /* audio AMR WB */ + { "\x00\x00\x00", 3, "audio/mp4" }, /* audio mp4 */ + { "\x2E\x73\x6E\x64", 4, "audio/au" } /* audio AU */ +}; + + +/*------------------------------------------------------------------------ + * Return the MIME type matching the data file. + * + * @param filename The name of file + * @param buf The data + * @param buflen The length of the data + * @return A MIME type string + */ +const char* file_mime_type( const char* filename, const char* buf, int buflen ) +{ + unsigned int i; + + /* check for matching magic headers */ + for ( i = 0; i < ARRAY_SIZE( mime_types ); i++ ) { + + if ( buflen < mime_types[i].magic_len ) /* data is shorter than size of magic */ + continue; + + if ( memcmp( buf, mime_types[i].magic, mime_types[i].magic_len ) == 0 ) + return mime_types[i].mime; + } + + /* we did not find the MIME type, so return the default (application/octet-stream) */ + return MIME_TYPE_OCTETSTREAM; +} + + +/*------------------------------------------------------------------------ + * Cleanup and deallocate a MXit file transfer object + * + * @param xfer The file transfer object + */ +static void mxit_xfer_free( PurpleXfer* xfer ) +{ + struct mxitxfer* mx = (struct mxitxfer*) xfer->data;; + + if ( mx ) { + g_free( mx ); + xfer->data = NULL; + } +} + + +/*======================================================================================================================== + * File Transfer callbacks + */ + +/*------------------------------------------------------------------------ + * Initialise a new file transfer. + * + * @param xfer The file transfer object + */ +static void mxit_xfer_init( PurpleXfer* xfer ) +{ + struct mxitxfer* mx = (struct mxitxfer*) xfer->data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_init\n" ); + + if ( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND ) { + /* we are trying to send a file to MXit */ + + if ( purple_xfer_get_size( xfer ) > CP_MAX_FILESIZE ) { + /* the file is too big */ + purple_xfer_error( xfer->type, xfer->account, xfer->who, _( "The file you are trying to send is too large!" ) ); + purple_xfer_cancel_local( xfer ); + return; + } + + /* start the file transfer */ + purple_xfer_start( xfer, -1, NULL, 0 ); + } + else { + /* + * we have just accepted a file transfer request from MXit. send a confirmation + * to the MXit server so that can send us the file + */ + mxit_send_file_accept( mx->session, mx->fileid, purple_xfer_get_size( xfer ), 0 ); + } +} + + +/*------------------------------------------------------------------------ + * Start the file transfer. + * + * @param xfer The file transfer object + */ +static void mxit_xfer_start( PurpleXfer* xfer ) +{ + unsigned char* buffer; + int size; + int wrote; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_start\n" ); + + if ( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND ) { + /* + * the user wants to send a file to one of his contacts. we need to create + * a buffer and copy the file data into memory and then we can send it to + * the contact. we will send the whole file with one go. + */ + buffer = g_malloc( xfer->bytes_remaining ); + size = fread( buffer, xfer->bytes_remaining, 1, xfer->dest_fp ); + + wrote = purple_xfer_write( xfer, buffer, xfer->bytes_remaining ); + if ( wrote > 0 ) + purple_xfer_set_bytes_sent( xfer, wrote ); + + /* free the buffer */ + g_free( buffer ); + buffer = NULL; + } +} + + +/*------------------------------------------------------------------------ + * The file transfer has ended. + * + * @param xfer The file transfer object + */ +static void mxit_xfer_end( PurpleXfer* xfer ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_end\n" ); + + /* deallocate object */ + mxit_xfer_free( xfer ); +} + + +/*------------------------------------------------------------------------ + * The file transfer (to a user) has been cancelled. + * + * @param xfer The file transfer object + */ +static void mxit_xfer_cancel_send( PurpleXfer* xfer ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_cancel_send\n" ); + + /* deallocate object */ + mxit_xfer_free( xfer ); +} + + +/*------------------------------------------------------------------------ + * Send the file data. + * + * @param buffer The data to sent + * @param size The length of the data to send + * @param xfer The file transfer object + * @return The amount of data actually sent + */ +static gssize mxit_xfer_write( const guchar* buffer, size_t size, PurpleXfer* xfer ) +{ + struct mxitxfer* mx = (struct mxitxfer*) xfer->data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_write\n" ); + + if ( !mx ) { + purple_debug_warning( MXIT_PLUGIN_ID, "mxit_xfer_write: invalid internal mxit xfer data\n" ); + return -1; + } + else if ( purple_xfer_get_type( xfer ) != PURPLE_XFER_SEND ) { + purple_debug_warning( MXIT_PLUGIN_ID, "mxit_xfer_write: wrong xfer type received\n" ); + return -1; + } + + /* create and send the packet to MXit */ + mxit_send_file( mx->session, purple_xfer_get_remote_user( xfer ), purple_xfer_get_filename( xfer ), buffer, size ); + + /* the transfer is complete */ + purple_xfer_set_completed( xfer, TRUE ); + + return size; +} + + +/*------------------------------------------------------------------------ + * The user has rejected a file offer from MXit. + * + * @param xfer The file transfer object + */ +static void mxit_xfer_request_denied( PurpleXfer* xfer ) +{ + struct mxitxfer* mx = (struct mxitxfer*) xfer->data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_request_denied\n" ); + + /* send file reject packet to MXit server */ + mxit_send_file_reject( mx->session, mx->fileid ); + + /* deallocate object */ + mxit_xfer_free( xfer ); +} + + +/*------------------------------------------------------------------------ + * The file transfer (from MXit) has been cancelled. + */ +static void mxit_xfer_cancel_recv( PurpleXfer* xfer ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_cancel_recv\n" ); + + /* deallocate object */ + mxit_xfer_free( xfer ); +} + + +/*======================================================================================================================== + * Callbacks from libPurple + */ + +/*------------------------------------------------------------------------ + * Indicate if file transfers are supported to this contact. + * For MXit file transfers are always supported. + * + * @param gc The connection object + * @param who The username of the contact + * @return TRUE if file transfers are supported + */ +gboolean mxit_xfer_enabled( PurpleConnection* gc, const char* who ) +{ + return TRUE; +} + + +/*------------------------------------------------------------------------ + * Create and initialize a new file transfer to a contact. + * + * @param gc The connection object + * @param who The username of the recipient + */ +PurpleXfer* mxit_xfer_new( PurpleConnection* gc, const char* who ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + PurpleXfer* xfer = NULL; + struct mxitxfer* mx = NULL; + + /* (reference: "libpurple/ft.h") */ + xfer = purple_xfer_new( session->acc, PURPLE_XFER_SEND, who ); + + /* create file info and attach it to the file transfer */ + mx = g_new0( struct mxitxfer, 1 ); + mx->session = session; + xfer->data = mx; + + /* configure callbacks (reference: "libpurple/ft.h") */ + purple_xfer_set_init_fnc( xfer, mxit_xfer_init ); + purple_xfer_set_start_fnc( xfer, mxit_xfer_start ); + purple_xfer_set_end_fnc( xfer, mxit_xfer_end ); + purple_xfer_set_cancel_send_fnc( xfer, mxit_xfer_cancel_send ); + purple_xfer_set_write_fnc( xfer, mxit_xfer_write ); + + return xfer; +} + + +/*------------------------------------------------------------------------ + * The user has initiated a file transfer to a contact. + * + * @param gc The connection object + * @param who The username of the contact + * @param filename The filename (is NULL if request has not been accepted yet) + */ +void mxit_xfer_tx( PurpleConnection* gc, const char* who, const char* filename ) +{ + PurpleXfer *xfer = mxit_xfer_new( gc, who ); + + if ( filename ) + purple_xfer_request_accepted( xfer, filename ); + else + purple_xfer_request( xfer ); +} + + +/*======================================================================================================================== + * Calls from the MXit Protocol layer + */ + +/*------------------------------------------------------------------------ + * A file transfer offer has been received from the MXit server. + * + * @param session The MXit session object + * @param usermame The username of the sender + * @param filename The name of the file being offered + * @param filesize The size of the file being offered + * @param fileid A unique ID that identifies this file + */ +void mxit_xfer_rx_offer( struct MXitSession* session, const char* username, const char* filename, int filesize, const char* fileid ) +{ + PurpleXfer* xfer = NULL; + struct mxitxfer* mx = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "File Offer: file=%s, from=%s, size=%i\n", filename, username, filesize ); + + xfer = purple_xfer_new( session->acc, PURPLE_XFER_RECEIVE, username ); + if ( xfer ) { + /* create a new mxit xfer struct for internal use */ + mx = g_new0( struct mxitxfer, 1 ); + mx->session = session; + memcpy( mx->fileid, fileid, MXIT_CHUNK_FILEID_LEN ); + xfer->data = mx; + + purple_xfer_set_filename( xfer, filename ); + if( filesize > 0 ) + purple_xfer_set_size( xfer, filesize ); + + /* register file transfer callback functions */ + purple_xfer_set_init_fnc( xfer, mxit_xfer_init ); + purple_xfer_set_request_denied_fnc( xfer, mxit_xfer_request_denied ); + purple_xfer_set_cancel_recv_fnc( xfer, mxit_xfer_cancel_recv ); + purple_xfer_set_end_fnc( xfer, mxit_xfer_end ); + + /* give the request to the user to accept/deny */ + purple_xfer_request( xfer ); + } +} + + +/*------------------------------------------------------------------------ + * Return the libPurple file-transfer object associated with a MXit transfer + * + * @param session The MXit session object + * @param fileid A unique ID that identifies this file + */ +static PurpleXfer* find_mxit_xfer( struct MXitSession* session, const char* fileid ) +{ + GList* item = NULL; + PurpleXfer* xfer = NULL; + + item = purple_xfers_get_all(); /* list of all active transfers */ + while ( item ) { + xfer = item->data; + + if ( xfer->account == session->acc ) { + /* transfer is associated with this MXit account */ + struct mxitxfer* mx = xfer->data; + + /* does the fileid match? */ + if ( ( mx ) && ( memcmp( mx->fileid, fileid, MXIT_CHUNK_FILEID_LEN ) == 0 ) ) + break; + } + + item = g_list_next( item ); + } + + if ( item ) + return item->data; + else + return NULL; +} + +/*------------------------------------------------------------------------ + * A file has been received from the MXit server. + * + * @param session The MXit session object + * @param fileid A unique ID that identifies this file + * @param data The file data + * @param datalen The size of the data + */ +void mxit_xfer_rx_file( struct MXitSession* session, const char* fileid, const char* data, int datalen ) +{ + PurpleXfer* xfer = NULL; + struct mxitxfer* mx = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_rx_file: (size=%i)\n", datalen ); + + /* find the file-transfer object */ + xfer = find_mxit_xfer( session, fileid ); + if ( xfer ) { + mx = xfer->data; + + /* this is the transfer we have been looking for */ + purple_xfer_ref( xfer ); + purple_xfer_start( xfer, -1, NULL, 0 ); + fwrite( data, datalen, 1, xfer->dest_fp ); + purple_xfer_unref( xfer ); + purple_xfer_set_completed( xfer, TRUE ); + purple_xfer_end( xfer ); + + /* inform MXit that file was successfully received */ + mxit_send_file_received( session, fileid, RECV_STATUS_SUCCESS ); + } + else { + /* file transfer not found */ + mxit_send_file_received( session, fileid, RECV_STATUS_BAD_ID ); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/filexfer.h Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,50 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- file transfers (sending and receiving) -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_FILEXFER_H_ +#define _MXIT_FILEXFER_H_ + + +/* + * a MXit file transfer + */ +struct mxitxfer { + struct MXitSession* session; + char fileid[MXIT_CHUNK_FILEID_LEN]; +}; + +const char* file_mime_type( const char* filename, const char* buf, int buflen ); + +/* libPurple callbacks */ +gboolean mxit_xfer_enabled( PurpleConnection* gc, const char* who ); +void mxit_xfer_tx( PurpleConnection* gc, const char* who, const char* filename ); +PurpleXfer* mxit_xfer_new( PurpleConnection* gc, const char* who ); + +/* MXit Protocol callbacks */ +void mxit_xfer_rx_offer( struct MXitSession* session, const char* username, const char* filename, int filesize, const char* fileid ); +void mxit_xfer_rx_file( struct MXitSession* session, const char* fileid, const char* data, int datalen ); + + +#endif /* _MXIT_FILEXFER_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/formcmds.c Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,397 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit Forms & Commands -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <string.h> +#include <glib.h> +#include <glib/gprintf.h> + +#include "purple.h" + +#include "protocol.h" +#include "mxit.h" +#include "markup.h" +#include "formcmds.h" + +#undef MXIT_DEBUG_COMMANDS + +/* + * the MXit Command identifiers + */ +typedef enum +{ + MXIT_CMD_UNKNOWN = 0, /* Unknown command */ + MXIT_CMD_CLRSCR, /* Clear screen (clrmsgscreen) */ + MXIT_CMD_SENDSMS, /* Send SMS (sendsms) */ + MXIT_CMD_REPLY, /* Reply (reply) */ + MXIT_CMD_PLATREQ, /* Platform Request (platreq) */ + MXIT_CMD_SELECTCONTACT, /* Select Contact (selc) */ + MXIT_CMD_IMAGE /* Inline image (img) */ +} MXitCommandType; + + +/* + * object for an inline image request with an URL + */ +struct ii_url_request +{ + struct RXMsgData* mx; + char* url; +}; + + +/*------------------------------------------------------------------------ + * Callback function invoked when an inline image request to a web site completes. + * + * @param url_data + * @param user_data The Markup message object + * @param url_text The data returned from the WAP site + * @param len The length of the data returned + * @param error_message Descriptive error message + */ +static void mxit_cb_ii_returned(PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message) +{ + struct ii_url_request* iireq = (struct ii_url_request*) user_data; + char* ii_data; + int* intptr = NULL; + int id; + +#ifdef MXIT_DEBUG_COMMANDS + purple_debug_info(MXIT_PLUGIN_ID, "Inline Image returned from %s\n", iireq->url); +#endif + + if (!url_text) { + /* no reply from the WAP site */ + purple_debug_error(MXIT_PLUGIN_ID, "Error downloading Inline Image from %s.\n", iireq->url); + goto done; + } + + /* lets first see if we dont have the inline image already in cache */ + if (g_hash_table_lookup(iireq->mx->session->iimages, iireq->url)) { + /* inline image found in the cache, so we just ignore this reply */ + goto done; + } + + /* make a copy of the data */ + ii_data = g_malloc(len); + memcpy(ii_data, (const char*) url_text, len); + + /* we now have the inline image, store it in the imagestore */ + id = purple_imgstore_add_with_id(ii_data, len, NULL); + + /* map the inline image id to purple image id */ + intptr = g_malloc(sizeof(int)); + *intptr = id; + g_hash_table_insert(iireq->mx->session->iimages, iireq->url, intptr); + + iireq->mx->flags |= PURPLE_MESSAGE_IMAGES; + +done: + iireq->mx->img_count--; + if ((iireq->mx->img_count == 0) && (iireq->mx->converted)) { + /* + * this was the last outstanding emoticon for this message, + * so we can now display it to the user. + */ + mxit_show_message(iireq->mx); + } + + g_free(iireq); +} + + +/*------------------------------------------------------------------------ + * Return the command identifier of this MXit Command. + * + * @param cmd The MXit command <key,value> map + * @return The MXit command identifier + */ +static MXitCommandType command_type(GHashTable* hash) +{ + char* op; + char* type; + + op = g_hash_table_lookup(hash, "op"); + if (op) { + if ( strcmp(op, "cmd") == 0 ) { + type = g_hash_table_lookup(hash, "type"); + if (type == NULL) /* no command provided */ + return MXIT_CMD_UNKNOWN; + else if (strcmp(type, "clrmsgscreen") == 0) /* clear the screen */ + return MXIT_CMD_CLRSCR; + else if (strcmp(type, "sendsms") == 0) /* send an SMS */ + return MXIT_CMD_SENDSMS; + else if (strcmp(type, "reply") == 0) /* list of options */ + return MXIT_CMD_REPLY; + else if (strcmp(type, "platreq") == 0) /* platform request */ + return MXIT_CMD_PLATREQ; + else if (strcmp(type, "selc") == 0) /* select contact */ + return MXIT_CMD_SELECTCONTACT; + } + else if (strcmp(op, "img") == 0) + return MXIT_CMD_IMAGE; + } + + return MXIT_CMD_UNKNOWN; +} + + +/*------------------------------------------------------------------------ + * Tokenize a MXit Command string into a <key,value> map. + * + * @param cmd The MXit command string + * @return The <key,value> hash-map, or NULL on error. + */ +static GHashTable* command_tokenize(char* cmd) +{ + GHashTable* hash = NULL; + gchar** parts; + gchar* part; + int i = 0; + +#ifdef MXIT_DEBUG_COMMANDS + purple_debug_info(MXIT_PLUGIN_ID, "command: '%s'\n", cmd); +#endif + + /* explode the command into parts */ + parts = g_strsplit(cmd, "|", 0); + + hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + + /* now break part into a key & value */ + while ((part = parts[i]) != NULL) { + char* value; + + value = strchr(parts[i], '='); /* find start of value */ + if (value != NULL) { + *value = '\0'; + value++; + } + +#ifdef MXIT_DEBUG_COMMANDS + purple_debug_info(MXIT_PLUGIN_ID, " key='%s' value='%s'\n", parts[i], value); +#endif + + g_hash_table_insert(hash, g_strdup(parts[i]), g_strdup(value)); + + i++; + } + + g_strfreev(parts); + + return hash; +} + + +/*------------------------------------------------------------------------ + * Process a ClearScreen MXit command. + * + * @param session The MXit session object + * @param from The sender of the message. + */ +static void command_clearscreen(struct MXitSession* session, const char* from) +{ + PurpleConversation *conv; + + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, from, session->acc); + if (conv == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Conversation with '%s' not found\n", from); + return; + } + + purple_conversation_clear_message_history(conv); // TODO: This doesn't actually clear the screen. +} + + +/*------------------------------------------------------------------------ + * Process a Reply MXit command. + * + * @param mx The received message data object + * @param hash The MXit command <key,value> map + */ +static void command_reply(struct RXMsgData* mx, GHashTable* hash) +{ + char* replymsg; + char* selmsg; + + selmsg = g_hash_table_lookup(hash, "selmsg"); /* find the selection message */ + replymsg = g_hash_table_lookup(hash, "replymsg"); /* find the reply message */ + if ((selmsg) && (replymsg)) { + gchar* seltext = g_markup_escape_text(purple_url_decode(selmsg), -1); + gchar* replytext = g_markup_escape_text(purple_url_decode(replymsg), -1); + + mxit_add_html_link( mx, replytext, seltext ); + + g_free(seltext); + g_free(replytext); + } +} + + +/*------------------------------------------------------------------------ + * Process a PlatformRequest MXit command. + * + * @param hash The MXit command <key,value> map + * @param msg The message to display (as generated so far) + */ +static void command_platformreq(GHashTable* hash, GString* msg) +{ + gchar* text = NULL; + char* selmsg; + char* dest; + + selmsg = g_hash_table_lookup(hash, "selmsg"); /* find the selection message */ + if (selmsg) { + text = g_markup_escape_text(purple_url_decode(selmsg), -1); + } + + dest = g_hash_table_lookup(hash, "dest"); /* find the destination */ + if (dest) { + g_string_append_printf(msg, "<a href=\"%s\">%s</a>", purple_url_decode(dest), (text) ? text : "Download"); /* add link to display message */ + } + + if (text) + g_free(text); +} + + +/*------------------------------------------------------------------------ + * Process an inline image MXit command. + * + * @param mx The received message data object + * @param hash The MXit command <key,value> map + * @param msg The message to display (as generated so far) + */ +static void command_image(struct RXMsgData* mx, GHashTable* hash, GString* msg) +{ + const char* img; + const char* reply; + guchar* rawimg; + char link[256]; + gsize rawimglen; + int imgid; + + img = g_hash_table_lookup(hash, "dat"); + if (img) { + rawimg = purple_base64_decode(img, &rawimglen); + //purple_util_write_data_to_file_absolute("/tmp/mxitinline.png", (char*) rawimg, rawimglen); + imgid = purple_imgstore_add_with_id(rawimg, rawimglen, NULL); + g_snprintf(link, sizeof(link), "<img id=\"%i\">", imgid); + g_string_append_printf(msg, "%s", link); + mx->flags |= PURPLE_MESSAGE_IMAGES; + } + else { + img = g_hash_table_lookup(hash, "src"); + if (img) { + struct ii_url_request* iireq; + + iireq = g_new0(struct ii_url_request,1); + iireq->url = g_strdup(purple_url_decode(img)); + iireq->mx = mx; + + g_string_append_printf(msg, "%s%s>", MXIT_II_TAG, iireq->url); + mx->got_img = TRUE; + + /* lets first see if we dont have the inline image already in cache */ + if (g_hash_table_lookup(mx->session->iimages, iireq->url)) { + /* inline image found in the cache, so we do not have to request it from the web */ + g_free(iireq); + } + else { + /* send the request for the inline image */ + purple_debug_info(MXIT_PLUGIN_ID, "sending request for inline image '%s'\n", iireq->url); + + /* request the image (reference: "libpurple/util.h") */ + purple_util_fetch_url_request(iireq->url, TRUE, NULL, TRUE, NULL, FALSE, mxit_cb_ii_returned, iireq); + mx->img_count++; + } + } + } + + /* if this is a clickable image, show a click link */ + reply = g_hash_table_lookup(hash, "replymsg"); + if (reply) { + g_string_append_printf(msg, "\n"); + mxit_add_html_link(mx, reply, "click here"); + } +} + + +/*------------------------------------------------------------------------ + * Process a received MXit Command message. + * + * @param mx The received message data object + * @param message The message text + * @return The length of the command + */ +//void mxit_command_received(struct MXitSession* session, const char* from, char* message, time_t timestamp) +int mxit_parse_command(struct RXMsgData* mx, char* message) +{ + GHashTable* hash = NULL; + char* start; + char* end; + + /* ensure that this is really a command */ + if ( ( message[0] != ':' ) || ( message[1] != ':' ) ) { + /* this is not a command */ + return 0; + } + + start = message + 2; + end = strstr(start, ":"); + if (end) { + /* end of a command found */ + *end = '\0'; /* terminate command string */ + + hash = command_tokenize(start); /* break into <key,value> pairs */ + if (hash) { + MXitCommandType type = command_type(hash); + + switch (type) { + case MXIT_CMD_CLRSCR : + command_clearscreen(mx->session, mx->from); + break; + case MXIT_CMD_REPLY : + command_reply(mx, hash); + break; + case MXIT_CMD_PLATREQ : + command_platformreq(hash, mx->msg); + break; + case MXIT_CMD_IMAGE : + command_image(mx, hash, mx->msg); + break; + default : + /* command unknown, or not currently supported */ + break; + } + g_hash_table_destroy(hash); + } + *end = ':'; + + return end - message; + } + else { + return 0; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/formcmds.h Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,35 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit Forms & Commands -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_FORMCMDS_H_ +#define _MXIT_FORMCMDS_H_ + +#include "mxit.h" + + +int mxit_parse_command(struct RXMsgData* mx, char* message); + + +#endif /* _MXIT_FORMCMDS_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/http.c Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,331 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit client protocol implementation -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "purple.h" + +#include "mxit.h" +#include "protocol.h" +#include "http.h" + + +/* HTTP constants */ +#define HTTP_11_200_OK "HTTP/1.1 200 OK\r\n" +#define HTTP_11_100_CONT "HTTP/1.1 100 Continue\r\n" +#define HTTP_11_SEPERATOR "\r\n\r\n" +#define HTTP_CONTENT_LEN "Content-Length: " + + +/* define to enable HTTP debugging */ +#define DEBUG_HTTP + + +/*------------------------------------------------------------------------ + * This will freeup the memory used by a HTTP request structure + * + * @param req The HTTP structure's resources should be freed up + */ +static void free_http_request( struct http_request* req ) +{ + g_free( req->host ); + g_free( req->data ); + g_free( req ); +} + + +/*------------------------------------------------------------------------ + * Write the request to the HTTP server. + * + * @param fd The file descriptor + * @param pktdata The packet data + * @param pktlen The length of the packet data + * @return Return -1 on error, otherwise 0 + */ +static int mxit_http_raw_write( int fd, const char* pktdata, int pktlen ) +{ + int written; + int res; + + written = 0; + while ( written < pktlen ) { + res = write( fd, &pktdata[written], pktlen - written ); + if ( res <= 0 ) { + /* error on socket */ + if ( errno == EAGAIN ) + continue; + + purple_debug_error( MXIT_PLUGIN_ID, "Error while writing packet to HTTP server (%i)\n", res ); + return -1; + } + written += res; + } + + return 0; +} + + +/*------------------------------------------------------------------------ + * Callback when data is received from the HTTP server. + * + * @param user_data The MXit session object + * @param source The file-descriptor on which data was received + * @param cond Condition which caused the callback (PURPLE_INPUT_READ) + */ +static void mxit_cb_http_read( gpointer user_data, gint source, PurpleInputCondition cond ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + char buf[256]; + int buflen; + char* body; + int bodylen; + char* ch; + int len; + char* tmp; + int res; + char* next; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_http_read\n" ); + + if ( session->rx_state == RX_STATE_RLEN ) { + /* we are reading in the HTTP headers */ + + /* copy partial headers if we have any part saved */ + memcpy( buf, session->rx_dbuf, session->rx_i ); + buflen = session->rx_i; + + /* read bytes from the socket */ + len = read( session->fd, buf + buflen, sizeof( buf ) - buflen ); + if ( len <= 0 ) { + /* connection has been terminated, or error occured */ + goto done; + } + +//nextpacket: + +#ifdef DEBUG_HTTP + purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST READ 1: (%i)\n", len ); + dump_bytes( session, buf + buflen, len ); +#endif + + /* see if we have all the HTTP headers yet */ + ch = strstr( buf, HTTP_11_SEPERATOR ); + if ( !ch ) { + /* we need to wait for more input, so save what we have */ + session->rx_i = buflen + len; + memcpy( session->rx_dbuf, buf, session->rx_i ); + return; + } + buflen += len; + + /* we have the header's end now skip over the http seperator to get the body offset */ + ch += strlen( HTTP_11_SEPERATOR ); + *(ch - 1) = '\0'; + body = ch; + + res = buflen - ( ch - buf ); + if ( res > 0 ) { + /* we read more bytes than just the header so copy it over */ + memcpy( session->rx_dbuf, ch, res ); + session->rx_i = res; + } + else { + session->rx_i = 0; + } + + /* test for a good response */ + if ( ( strncmp( buf, HTTP_11_200_OK, strlen( HTTP_11_200_OK ) ) != 0 ) && ( strncmp( buf, HTTP_11_100_CONT, strlen( HTTP_11_100_CONT ) ) != 0 ) ) { + /* bad result */ + purple_debug_error( MXIT_PLUGIN_ID, "HTTP error: %s\n", ch ); + goto done; + } + + /* find the content-length */ + ch = (char*) purple_strcasestr( buf, HTTP_CONTENT_LEN ); + if ( !ch ) { + /* bad request. it does not contain a content-length header */ + purple_debug_error( MXIT_PLUGIN_ID, "HTTP reply received without content-length header (ignoring packet)\n" ); + goto done; + } + + /* parse the content-length */ + ch += strlen( HTTP_CONTENT_LEN ); + tmp = strchr( ch, '\r' ); + if ( !tmp ) { + purple_debug_error( MXIT_PLUGIN_ID, "Received bad HTTP reply packet (ignoring packet)\n" ); + goto done; + } + tmp = g_strndup( ch, tmp - ch ); + bodylen = atoi( tmp ); + g_free( tmp ); + tmp = NULL; + + if ( buflen > ( ( body - buf ) + bodylen ) ) { + /* we have a second packet here */ + next = body + bodylen; + session->rx_res = 0; + } + else { + session->rx_res = bodylen - session->rx_i; + } + + if ( session->rx_res == 0 ) { + /* we have read all the data */ + session->rx_i = bodylen; + session->rx_state = RX_STATE_PROC; + } + else { + /* there is still some data outstanding */ + session->rx_state = RX_STATE_DATA; + } + } + else if ( session->rx_state == RX_STATE_DATA ) { + /* we are reading the HTTP content (body) */ + + /* read bytes from the socket */ + len = read( session->fd, &session->rx_dbuf[session->rx_i], session->rx_res ); + if ( len <= 0 ) { + /* connection has been terminated, or error occured */ + goto done; + } + +#ifdef DEBUG_HTTP + purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST READ 2: (%i)\n", len ); + dump_bytes( session, &session->rx_dbuf[session->rx_i], len ); +#endif + session->rx_i += len; + session->rx_res -= len; + + if ( session->rx_res == 0 ) { + /* ok, so now we have read in the whole packet */ + session->rx_state = RX_STATE_PROC; + } + } + + if ( session->rx_state == RX_STATE_PROC ) { + mxit_parse_packet( session ); + +#if 0 + if ( next ) { + /* there is another packet of which we read some data */ + + /* reset input */ + session->rx_state = RX_STATE_RLEN; + session->rx_lbuf[0] = '\0'; + session->rx_i = 0; + session->rx_res = 0; + + /* move read data */ + len = next - buf; + buflen = len; + memcpy( buf, next, len ); + goto nextpacket; + } +#endif + + /* we are done */ + goto done; + } + + return; +done: + close( session->fd ); + purple_input_remove( session->http_handler ); + session->http_handler = 0; +} + + +/*------------------------------------------------------------------------ + * Callback invoked once the connection has been established to the HTTP server, + * or on connection failure. + * + * @param user_data The MXit session object + * @param source The file-descriptor associated with the connection + * @param error_message Message explaining why the connection failed + */ +static void mxit_cb_http_connect( gpointer user_data, gint source, const gchar* error_message ) +{ + struct http_request* req = (struct http_request*) user_data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_http_connect\n" ); + + /* source is the file descriptor of the new connection */ + if ( source < 0 ) { + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_http_connect failed: %s\n", error_message ); + purple_connection_error( req->session->con, _( "Unable to connect to the mxit HTTP server. Please check your server server settings." ) ); + return; + } + + /* we now have an open and active TCP connection to the mxit server */ + req->session->fd = source; + + /* reset the receive buffer */ + req->session->rx_state = RX_STATE_RLEN; + req->session->rx_lbuf[0] = '\0'; + req->session->rx_i = 0; + req->session->rx_res = 0; + + /* start listening on the open connection for messages from the server (reference: "libpurple/eventloop.h") */ + req->session->http_handler = purple_input_add( req->session->fd, PURPLE_INPUT_READ, mxit_cb_http_read, req->session ); + + /* actually send the request to the HTTP server */ + mxit_http_raw_write( req->session->fd, req->data, req->datalen ); + + /* free up resources */ + free_http_request( req ); + req = NULL; +} + + +/*------------------------------------------------------------------------ + * Create HTTP connection for sending a HTTP request + * + * @param session The MXit session object + * @param host The server name to connect to + * @param port The port number to connect to + * @param data The HTTP request data (including HTTP headers etc.) + * @param datalen The HTTP request data length + */ +void mxit_http_send_request( struct MXitSession* session, char* host, int port, const char* data, int datalen ) +{ + PurpleProxyConnectData* con = NULL; + struct http_request* req; + + /* build the http request */ + req = g_new0( struct http_request, 1 ); + req->session = session; + req->host = host; + req->port = port; + req->data = g_malloc0( datalen ); + memcpy( req->data, data, datalen ); + req->datalen = datalen; + + /* open connection to the HTTP server */ + con = purple_proxy_connect( NULL, session->acc, host, port, mxit_cb_http_connect, req ); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/http.h Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,47 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit client protocol implementation -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + + +#ifndef _MXIT_HTTP_H_ +#define _MXIT_HTTP_H_ + + + +struct http_request +{ + struct MXitSession* session; + char* host; + int port; + char* data; + int datalen; +}; + + +void mxit_http_send_request( struct MXitSession* session, char* host, int port, const char* data, int datalen ); + + + +#endif /* _MXIT_HTTP_H_ */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/login.c Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,789 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit user login functionality -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <string.h> + +#include "purple.h" + +#include "protocol.h" +#include "mxit.h" +#include "cipher.h" +#include "login.h" +#include "profile.h" + +/* requesting captcha size */ +#define MXIT_CAPTCHA_HEIGHT 50 +#define MXIT_CAPTCHA_WIDTH 150 + + +/* prototypes */ +static void mxit_register_view( struct MXitSession* session ); +static void get_clientinfo( struct MXitSession* session ); + + +/*------------------------------------------------------------------------ + * Create a new mxit session object + * + * @return The MXit session object + */ +static struct MXitSession* mxit_create_object( PurpleAccount* account ) +{ + struct MXitSession* session = NULL; + PurpleConnection* con = NULL; + + /* currently the wapsite does not handle a '+' in front of the username (mxitid) so we just strip it */ + if ( account->username[0] == '+' ) { + char* fixed; + + /* cut off the '+' */ + fixed = g_strdup( &account->username[1] ); + purple_account_set_username( account, fixed ); + g_free( fixed ); + } + + session = g_new0( struct MXitSession, 1 ); + + /* configure the connection (reference: "libpurple/connection.h") */ + con = purple_account_get_connection( account ); + con->proto_data = session; + con->flags |= PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_HTML; + session->con = con; + + /* add account */ + session->acc = account; + + /* configure the session (reference: "libpurple/account.h") */ + g_strlcpy( session->server, purple_account_get_string( account, MXIT_CONFIG_SERVER_ADDR, DEFAULT_SERVER ), sizeof( session->server ) ); + g_strlcpy( session->http_server, purple_account_get_string( account, MXIT_CONFIG_HTTPSERVER, DEFAULT_HTTP_SERVER ), sizeof( session->http_server ) ); + session->port = purple_account_get_int( account, MXIT_CONFIG_SERVER_PORT, DEFAULT_PORT ); + g_strlcpy( session->distcode, purple_account_get_string( account, MXIT_CONFIG_DISTCODE, "" ), sizeof( session->distcode ) ); + g_strlcpy( session->clientkey, purple_account_get_string( account, MXIT_CONFIG_CLIENTKEY, "" ), sizeof( session->clientkey ) ); + g_strlcpy( session->dialcode, purple_account_get_string( account, MXIT_CONFIG_DIALCODE, "" ), sizeof( session->dialcode ) ); + session->http = purple_account_get_bool( account, MXIT_CONFIG_USE_HTTP, FALSE ); + session->iimages = g_hash_table_new( g_str_hash, g_str_equal ); + session->rx_state = RX_STATE_RLEN; + session->http_interval = MXIT_HTTP_POLL_MIN; + session->http_last_poll = time( NULL ); + + return session; +} + + +/*------------------------------------------------------------------------ + * We now have a connection established with MXit, so we can start the + * login procedure + * + * @param session The MXit session object + */ +static void mxit_connected( struct MXitSession* session ) +{ + int state; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_connected\n" ); + + session->flags |= MXIT_FLAG_CONNECTED; + purple_connection_update_progress( session->con, _( "Logging In..." ), 2, 4 ); + + /* create a timer to send a ping packet if the connection is idle */ + session->last_tx = time( NULL ); + + /* encrypt the user password */ + session->encpwd = mxit_encrypt_password( session ); + + state = purple_account_get_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN ); + if ( state == MXIT_STATE_LOGIN ) { + /* create and send login packet */ + mxit_send_login( session ); + } + else { + if ( !session->profile ) { + /* we have lost the session profile, so ask the user to enter it again */ + mxit_register_view( session ); + } + else { + /* create and send the register packet */ + mxit_send_register( session ); + } + } + + /* enable signals */ + mxit_enable_signals( session ); + +#ifdef MXIT_LINK_CLICK + /* register for uri click notification */ + mxit_register_uri_handler(); +#endif + + /* start the polling if this is a HTTP connection */ + if ( session->http ) { + session->http_timer_id = purple_timeout_add_seconds( 2, mxit_manage_polling, session ); + } + + /* start the tx queue manager timer */ + session->q_timer = purple_timeout_add_seconds( 2, mxit_manage_queue, session ); +} + + +/*------------------------------------------------------------------------ + * Callback invoked once the connection has been established to the MXit server, + * or on connection failure. + * + * @param user_data The MXit session object + * @param source The file-descriptor associated with the connection + * @param error_message Message explaining why the connection failed + */ +static void mxit_cb_connect( gpointer user_data, gint source, const gchar* error_message ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_connect\n" ); + + /* source is the file descriptor of the new connection */ + if ( source < 0 ) { + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_connect failed: %s\n", error_message ); + purple_connection_error( session->con, _( "Unable to connect to the mxit server. Please check your server server settings." ) ); + return; + } + + /* we now have an open and active TCP connection to the mxit server */ + session->fd = source; + + /* start listening on the open connection for messages from the server (reference: "libpurple/eventloop.h") */ + session->con->inpa = purple_input_add( session->fd, PURPLE_INPUT_READ, mxit_cb_rx, session ); + + mxit_connected( session ); +} + + +/*------------------------------------------------------------------------ + * Attempt to establish a connection to the MXit server. + * + * @param session The MXit session object + */ +static void mxit_login_connect( struct MXitSession* session ) +{ + PurpleProxyConnectData* data = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_login_connect\n" ); + + purple_connection_update_progress( session->con, _( "Connecting..." ), 1, 4 ); + + /* + * at this stage we have all the user's information we require + * for logging into MXit. we will now create a new connection to + * a MXit server. + */ + + if ( !session->http ) { + /* socket connection */ + data = purple_proxy_connect( session->con, session->acc, session->server, session->port, mxit_cb_connect, session ); + if ( !data ) { + purple_connection_error( session->con, _( "Unable to connect to the mxit server. Please check your server server settings." ) ); + return; + } + } + else { + /* http connection */ + mxit_connected( session ); + } +} + + +/*------------------------------------------------------------------------ + * Register a new account with MXit + * + * @param gc The connection object + * @param fields This is the fields filled-in by the user + */ +static void mxit_cb_register_ok( PurpleConnection *gc, PurpleRequestFields *fields ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + struct MXitProfile* profile = session->profile; + const char* str; + const char* pin; + char* err = NULL; + int len; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_register_ok\n" ); + + if ( !PURPLE_CONNECTION_IS_VALID( gc ) ) { + purple_debug_error( MXIT_PLUGIN_ID, "Unable to register; account offline.\n" ); + return; + } + + /* nickname */ + str = purple_request_fields_get_string( fields, "nickname" ); + if ( ( !str ) || ( strlen( str ) < 3 ) ) { + err = "The nick name you entered is invalid."; + goto out; + } + g_strlcpy( profile->nickname, str, sizeof( profile->nickname ) ); + + /* birthdate */ + str = purple_request_fields_get_string( fields, "bday" ); + if ( ( !str ) || ( strlen( str ) < 10 ) || ( !validateDate( str ) ) ) { + err = "The birthday you entered is invalid. The correct format is: 'YYYY-MM-DD'."; + goto out; + } + g_strlcpy( profile->birthday, str, sizeof( profile->birthday ) ); + + /* gender */ + if ( purple_request_fields_get_choice( fields, "male" ) == 0 ) + profile->male = FALSE; + else + profile->male = TRUE; + + /* pin */ + pin = purple_request_fields_get_string( fields, "pin" ); + if ( !pin ) { + err = "The PIN you entered is invalid."; + goto out; + } + len = strlen( pin ); + if ( ( len < 7 ) || ( len > 10 ) ) { + err = "The PIN you entered has an invalid length [7-10]."; + goto out; + } + for ( i = 0; i < len; i++ ) { + if ( !g_ascii_isdigit( pin[i] ) ) { + err = "The PIN is invalid. It should only consist of digits [0-9]."; + goto out; + } + } + str = purple_request_fields_get_string( fields, "pin2" ); + if ( ( !str ) || ( strcmp( pin, str ) != 0 ) ) { + err = "The two PINs you entered does not match."; + goto out; + } + g_strlcpy( profile->pin, pin, sizeof( profile->pin ) ); + +out: + if ( !err ) { + purple_account_set_password( session->acc, session->profile->pin ); + mxit_login_connect( session ); + } + else { + /* show error to user */ + mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Registration Error" ), _( err ) ); + mxit_register_view( session ); + } +} + + +/*------------------------------------------------------------------------ + * Register a new account with MXit + * + * @param gc The connection object + * @param fields This is the fields filled-in by the user + */ +static void mxit_cb_register_cancel( PurpleConnection *gc, PurpleRequestFields *fields ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_register_cancel\n" ); + + /* disconnect */ + purple_account_disconnect( gc->account ); +} + + +/*------------------------------------------------------------------------ + * Show a window to the user so that he can enter his information + * + * @param session The MXit session object + */ +static void mxit_register_view( struct MXitSession* session ) +{ + struct MXitProfile* profile; + PurpleRequestFields* fields; + PurpleRequestFieldGroup* group; + PurpleRequestField* field; + + if ( !session->profile ) { + /* we need to create a profile object here */ + session->profile = g_new0( struct MXitProfile, 1 ); + } + profile = session->profile; + + fields = purple_request_fields_new(); + group = purple_request_field_group_new( NULL ); + purple_request_fields_add_group( fields, group ); + + /* mxit login name */ + field = purple_request_field_string_new( "loginname", _( "MXit Login Name" ), purple_account_get_username( session->acc ), FALSE ); + purple_request_field_string_set_editable( field, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* nick name */ + field = purple_request_field_string_new( "nickname", _( "Nick Name" ), profile->nickname, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* birthday */ + field = purple_request_field_string_new( "bday", _( "Birthday" ), profile->birthday, FALSE ); + purple_request_field_string_set_default_value( field, "YYYY-MM-DD" ); + purple_request_field_group_add_field( group, field ); + + /* gender */ + field = purple_request_field_choice_new( "male", _( "Gender" ), ( profile->male ) ? 1 : 0 ); + purple_request_field_choice_add( field, _( "Female" ) ); /* 0 */ + purple_request_field_choice_add( field, _( "Male" ) ); /* 1 */ + purple_request_field_group_add_field( group, field ); + + /* pin */ + field = purple_request_field_string_new( "pin", _( "PIN" ), profile->pin, FALSE ); + purple_request_field_string_set_masked( field, TRUE ); + purple_request_field_group_add_field( group, field ); + field = purple_request_field_string_new( "pin2", _( "Verify PIN" ), "", FALSE ); + purple_request_field_string_set_masked( field, TRUE ); + purple_request_field_group_add_field( group, field ); + + /* show the form to the user to complete */ + purple_request_fields( session->con, _( "Register New MXit Account" ), _( "Register New MXit Account" ), _( "Please fill in the following fields:" ), fields, _( "OK" ), G_CALLBACK( mxit_cb_register_ok ), _( "Cancel" ), G_CALLBACK( mxit_cb_register_cancel ), session->acc, NULL, NULL, session->con ); +} + + +/*------------------------------------------------------------------------ + * Callback function invoked once the Authorization information has been submitted + * to the MXit WAP site. + * + * @param url_data libPurple internal object (see purple_util_fetch_url_request) + * @param user_data The MXit session object + * @param url_text The data returned from the WAP site + * @param len The length of the data returned + * @param error_message Descriptive error message + */ +static void mxit_cb_clientinfo2( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + gchar** parts; + gchar** host; + int state; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_clientinfo_cb2\n" ); + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "HTTP RESPONSE: '%s'\n", url_text ); +#endif + + if ( !url_text ) { + /* no reply from the WAP site */ + purple_connection_error( session->con, _( "Error contacting the MXit WAP site. Please try again later." ) ); + return; + } + + /* explode the response from the WAP site into an array */ + parts = g_strsplit( url_text, ";", 15 ); + + if ( !parts ) { + /* wapserver error */ + purple_connection_error( session->con, _( "MXit is currently unable to process the request. Please try again later." ) ); + return; + } + + /* check wapsite return code */ + switch ( parts[0][0] ) { + case '0' : + /* valid reply! */ + break; + case '1' : + purple_connection_error( session->con, _( "Wrong security code entered. Please try again later." ) ); + return; + case '2' : + purple_connection_error( session->con, _( "Your session has expired. Please try again later." ) ); + return; + case '5' : + purple_connection_error( session->con, _( "Invalid country selected. Please try again." ) ); + return; + case '6' : + purple_connection_error( session->con, _( "Username is not registered. Please register first." ) ); + return; + case '7' : + purple_connection_error( session->con, _( "Username is already registered. Please choose another username." ) ); + /* this user's account already exists, so we need to change the registration login flag to be login */ + purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN ); + return; + case '3' : + case '4' : + default : + purple_connection_error( session->con, _( "Internal error. Please try again later." ) ); + return; + } + + /* now parse and split the distribution code and the client key */ + g_strlcpy( session->distcode, &parts[1][2], 36 + 1 ); + g_strlcpy( session->clientkey, &parts[1][38], 8 + 1 ); + + /* get the dial code for the client */ + g_strlcpy( session->dialcode, parts[4], sizeof( session->dialcode ) ); + + /* parse the proxy server address and port number */ + host = g_strsplit( parts[2], ":", 4 ); + g_strlcpy( session->server, &host[1][2], sizeof( session->server ) ); + session->port = atoi( &host[2][0] ); + + /* parse the http proxy server address and port number */ + g_strlcpy( session->http_server, parts[3], sizeof( session->http_server ) ); + + purple_debug_info( MXIT_PLUGIN_ID, "distcode='%s', clientkey='%s', dialcode='%s'\n", session->distcode, session->clientkey, session->dialcode ); + purple_debug_info( MXIT_PLUGIN_ID, "sock_server='%s', http_server='%s', port='%i', cc='%s'\n", session->server, session->http_server, session->port, parts[11] ); + + /* save the information (reference: "libpurple/account.h") */ + purple_account_set_string( session->acc, MXIT_CONFIG_DISTCODE, session->distcode ); + purple_account_set_string( session->acc, MXIT_CONFIG_CLIENTKEY, session->clientkey ); + purple_account_set_string( session->acc, MXIT_CONFIG_DIALCODE, session->dialcode ); + purple_account_set_string( session->acc, MXIT_CONFIG_SERVER_ADDR, session->server ); + purple_account_set_int( session->acc, MXIT_CONFIG_SERVER_PORT, session->port ); + purple_account_set_string( session->acc, MXIT_CONFIG_HTTPSERVER, session->http_server ); + + /* update the state */ + state = purple_account_get_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN ); + if ( state == MXIT_STATE_REGISTER1 ) + purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_REGISTER2 ); + + /* freeup the memory */ + g_strfreev( host ); + g_strfreev( parts ); + + if ( state == MXIT_STATE_LOGIN ) { + /* now we can continue with the login process */ + mxit_login_connect( session ); + } + else { + /* the user is registering so we need to get more information from him/her first to complete the process */ + mxit_register_view( session ); + } +} + + +/*------------------------------------------------------------------------ + * Free up the data associated with the Authorization process. + * + * @param data The data object to free + */ +static void free_logindata( struct login_data* data ) +{ + if ( !data ) + return; + + /* free up the login resources */ + g_free( data->wapserver ); + g_free( data->sessionid ); + g_free( data->captcha ); + g_free( data->cc ); + g_free( data->locale ); + g_free( data ); +} + + +/*------------------------------------------------------------------------ + * This function is called when the user accepts the Authorization form. + * + * @param gc The connection object + * @param fields The list of fields in the accepted form + */ +static void mxit_cb_captcha_ok( PurpleConnection* gc, PurpleRequestFields* fields ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + PurpleUtilFetchUrlData* url_data; + PurpleRequestField* field; + const char* captcha_resp; + GList* entries; + GList* entry; + char* url; + int state; + + /* get the captcha response */ + captcha_resp = purple_request_fields_get_string( fields, "code" ); + if ( ( captcha_resp == NULL ) || ( captcha_resp[0] == '\0' ) ) { + /* the user did not fill in the captcha */ + mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Error" ), _( "You did not enter the security code" ) ); + free_logindata( session->logindata ); + purple_account_disconnect( session->acc ); + return; + } + + /* get chosen country */ + field = purple_request_fields_get_field( fields, "country" ); + entries = purple_request_field_list_get_selected( field ); + entry = g_list_first( entries ); + session->logindata->cc = purple_request_field_list_get_data( field, entry->data ); + purple_account_set_string( session->acc, MXIT_CONFIG_COUNTRYCODE, session->logindata->cc ); + + /* get chosen language */ + field = purple_request_fields_get_field( fields, "locale" ); + entries = purple_request_field_list_get_selected( field ); + entry = g_list_first( entries ); + session->logindata->locale = purple_request_field_list_get_data( field, entry->data ); + purple_account_set_string( session->acc, MXIT_CONFIG_LOCALE, session->logindata->locale ); + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "cc='%s', locale='%s', captcha='%s'\n", session->logindata->cc, session->logindata->locale, captcha_resp ); +#endif + + /* get state */ + state = purple_account_get_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN ); + + url = g_strdup_printf( "%s?type=getpid&sessionid=%s&login=%s&ver=%s&clientid=%s&cat=%s&chalresp=%s&cc=%s&loc=%s&path=%i&brand=%s&model=%s&h=%i&w=%i&ts=%li", + session->logindata->wapserver, session->logindata->sessionid, purple_url_encode( session->acc->username ), MXIT_CP_RELEASE, MXIT_CLIENT_ID, MXIT_CP_ARCH, + captcha_resp, session->logindata->cc, session->logindata->locale, ( state == MXIT_STATE_REGISTER1 ) ? 0 : 1, MXIT_CP_PLATFORM, MXIT_CP_OS, + MXIT_CAPTCHA_HEIGHT, MXIT_CAPTCHA_WIDTH, time( NULL ) ); + url_data = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_clientinfo2, session ); + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "HTTP REQUEST: '%s'\n", url ); +#endif + g_free( url ); + + /* free up the login resources */ + free_logindata( session->logindata ); +} + + +/*------------------------------------------------------------------------ + * This function is called when the user cancels the Authorization form. + * + * @param gc The connection object + * @param fields The list of fields in the cancelled form + */ +static void mxit_cb_captcha_cancel( PurpleConnection* gc, PurpleRequestFields* fields ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + + /* free up the login resources */ + free_logindata( session->logindata ); + + /* we cannot continue, so we disconnect this account */ + purple_account_disconnect( session->acc ); +} + + +/*------------------------------------------------------------------------ + * Callback function invoked once the client information has been retrieved from + * the MXit WAP site. Display page where user can select their authorization information. + * + * @param url_data libPurple internal object (see purple_util_fetch_url_request) + * @param user_data The MXit session object + * @param url_text The data returned from the WAP site + * @param len The length of the data returned + * @param error_message Descriptive error message + */ +static void mxit_cb_clientinfo1( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + struct login_data* logindata; + PurpleRequestFields* fields; + PurpleRequestFieldGroup* group = NULL; + PurpleRequestField* field = NULL; + gchar** parts; + gchar** countries; + gchar** locales; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_clientinfo_cb1\n" ); + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "RESPONSE: %s\n", url_text ); +#endif + + if ( !url_text ) { + /* no reply from the WAP site */ + purple_connection_error( session->con, _( "Error contacting the MXit WAP site. Please try again later." ) ); + return; + } + + /* explode the response from the WAP site into an array */ + parts = g_strsplit( url_text, ";", 15 ); + + if ( ( !parts ) || ( parts[0][0] != '0' ) ) { + /* server could not find the user */ + purple_connection_error( session->con, _( "MXit is currently unable to process the request. Please try again later." ) ); + return; + } + + /* save received settings */ + logindata = g_new0( struct login_data, 1 ); + logindata->wapserver = g_strdup( parts[1] ); + logindata->sessionid = g_strdup( parts[2] ); + session->logindata = logindata; + + /* now generate the popup requesting the user for action */ + + fields = purple_request_fields_new(); + group = purple_request_field_group_new( NULL ); + purple_request_fields_add_group( fields, group ); + + /* add the captcha */ + logindata->captcha = purple_base64_decode( parts[3], &logindata->captcha_size ); + field = purple_request_field_image_new( "capcha", _( "Security Code" ), (gchar*) logindata->captcha, logindata->captcha_size ); + purple_request_field_group_add_field( group, field ); + + /* ask for input */ + field = purple_request_field_string_new( "code", _( "Enter Security Code" ), NULL, FALSE ); + purple_request_field_group_add_field( group, field ); + + /* choose your country, but be careful, we already know your IP! ;-) */ + countries = g_strsplit( parts[4], ",", 500 ); + field = purple_request_field_list_new( "country", _( "Your Country" ) ); + purple_request_field_list_set_multi_select( field, FALSE ); + for ( i = 0; countries[i]; i++ ) { + gchar** country; + + country = g_strsplit( countries[i], "|", 2 ); + if ( !country ) { + /* oops, this is not good, time to bail */ + break; + } + purple_request_field_list_add( field, country[1], g_strdup( country[0] ) ); + if ( strcmp( country[1], parts[6] ) == 0 ) { + /* based on the user's ip, this is his current country code, so we default to it */ + purple_request_field_list_add_selected( field, country[1] ); + } + g_strfreev( country ); + } + purple_request_field_group_add_field( group, field ); + + /* choose your language */ + locales = g_strsplit( parts[5], ",", 200 ); + field = purple_request_field_list_new( "locale", _( "Your Language" ) ); + purple_request_field_list_set_multi_select( field, FALSE ); + for ( i = 0; locales[i]; i++ ) { + gchar** locale; + + locale = g_strsplit( locales[i], "|", 2 ); + if ( !locale ) { + /* oops, this is not good, time to bail */ + break; + } + purple_request_field_list_add( field, locale[1], g_strdup( locale[0] ) ); + g_strfreev( locale ); + } + purple_request_field_list_add_selected( field, "English" ); + purple_request_field_group_add_field( group, field ); + + /* display the form to the user and wait for his/her input */ + purple_request_fields( session->con, "MXit", _( "MXit Authorization" ), _( "MXit account validation" ), fields, + _( "Continue" ), G_CALLBACK( mxit_cb_captcha_ok ), _( "Cancel" ), G_CALLBACK( mxit_cb_captcha_cancel ), session->acc, NULL, NULL, session->con ); + + /* freeup the memory */ + g_strfreev( parts ); +} + + +/*------------------------------------------------------------------------ + * Initiate a request for the client information (distribution code, client key, etc) + * required for logging in from the MXit WAP site. + * + * @param session The MXit session object + */ +static void get_clientinfo( struct MXitSession* session ) +{ + PurpleUtilFetchUrlData* url_data; + const char* wapserver; + char* url; + + purple_debug_info( MXIT_PLUGIN_ID, "get_clientinfo\n" ); + + purple_connection_update_progress( session->con, _( "Retrieving User Information..." ), 0, 4 ); + + /* get the WAP site as was configured by the user in the advanced settings */ + wapserver = purple_account_get_string( session->acc, MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE ); + + /* reference: "libpurple/util.h" */ + url = g_strdup_printf( "%s/res/?type=challenge&getcountries=true&getlanguage=true&getimage=true&h=%i&w=%i&ts=%li", wapserver, MXIT_CAPTCHA_HEIGHT, MXIT_CAPTCHA_WIDTH, time( NULL ) ); + url_data = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_clientinfo1, session ); + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "HTTP REQUEST: '%s'\n", url ); +#endif + g_free( url ); +} + + +/*------------------------------------------------------------------------ + * Log the user into MXit. + * + * @param account The account object + */ +void mxit_login( PurpleAccount* account ) +{ + struct MXitSession* session = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_login\n" ); + + /* create and save a new mxit session */ + session = mxit_create_object( account ); + + /* + * before we can login we need to have a valid distribution code and client key for authentication. + * if we don't have any info saved from a previous login, we need to get it from the MXit WAP site. + * we do cache it, so this step is only done on the very first login for each account. + */ + if ( ( session->distcode == NULL ) || ( strlen( session->distcode ) == 0 ) ) { + /* this must be the very first login, so we need to retrieve the user information */ + get_clientinfo( session ); + } + else { + /* we can continue with the login */ + mxit_login_connect( session ); + } +} + + +/*------------------------------------------------------------------------ + * Perform a reconnect to the MXit server, and maintain same session object. + * + * @param account The account object + */ +void mxit_reconnect( struct MXitSession* session ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "mxit_reconnect\n" ); + + /* close existing connection */ + session->flags &= ~MXIT_FLAG_CONNECTED; + purple_proxy_connect_cancel_with_handle( session->con ); + + /* perform the re-connect */ + mxit_login_connect( session ); +} + + +/*------------------------------------------------------------------------ + * Register a new account with MXit + * + * @param acc The account object + */ +void mxit_register( PurpleAccount* account ) +{ + struct MXitSession* session = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_register\n" ); + + /* create and save a new mxit session */ + session = mxit_create_object( account ); + purple_account_set_int( account, MXIT_CONFIG_STATE, MXIT_STATE_REGISTER1 ); + + get_clientinfo( session ); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/login.h Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,45 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit user login functionality -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_LOGIN_H_ +#define _MXIT_LOGIN_H_ + + +struct login_data { + char* wapserver; /* direct WAP server for postback */ + char* sessionid; /* unique session id */ + guchar* captcha; /* actual captcha (PNG) */ + gsize captcha_size; /* captcha size */ + char* cc; /* country code */ + char* locale; /* locale (language) */ +}; + + +void mxit_login( PurpleAccount* account ); +void mxit_register( PurpleAccount* account ); +void mxit_reconnect( struct MXitSession* session ); + + +#endif /* _MXIT_LOGIN_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/markup.c Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,1192 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- convert between MXit and libPurple markup -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#include "purple.h" + +#include "protocol.h" +#include "mxit.h" +#include "markup.h" +#include "chunk.h" +#include "formcmds.h" +#include "roster.h" + + +/* define this to enable emoticon (markup) debugging */ +#undef MXIT_DEBUG_EMO +/* define this to enable markup conversion debugging */ +#undef MXIT_DEBUG_MARKUP + + +#define MXIT_FRAME_MAGIC "MXF\x01" /* mxit emoticon magic number */ +#define MXIT_MAX_EMO_ID 16 /* maximum emoticon ID length */ +#define COLORCODE_LEN 6 /* colour code ID length */ + + +/* HTML tag types */ +#define MXIT_TAG_COLOR 0x01 /* font color tag */ +#define MXIT_TAG_SIZE 0x02 /* font size tag */ +#define MXIT_MAX_MSG_TAGS 90 /* maximum tags per message (pigdin hack work around) */ + +/* + * a HTML tag object + */ +struct tag { + char type; + char* value; +}; + + +#define MXIT_VIBE_MSG_COLOR "#9933FF" + +/* vibes */ +static const char* vibes[] = { + /* 0 */ "Cool Vibrations", + /* 1 */ "Purple Rain", + /* 2 */ "Polite", + /* 3 */ "Rock n Roll", + /* 4 */ "Summer Slumber", + /* 5 */ "Electric Razor", + /* 6 */ "S.O.S", + /* 7 */ "Jack Hammer", + /* 8 */ "Bumble Bee", + /* 9 */ "Ripple" +}; + + + +#ifdef MXIT_DEBUG_EMO +/*------------------------------------------------------------------------ + * Dump a byte buffer as hexadecimal to the console for debugging purposes. + * + * @param buf The data to dump + * @param len The length of the data + */ +static void hex_dump( const char* buf, int len ) +{ + char msg[256]; + int pos; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "Dumping data (%i bytes)\n", len ); + + memset( msg, 0x00, sizeof( msg ) ); + pos = 0; + + for ( i = 0; i < len; i++ ) { + + if ( pos == 0 ) + pos += sprintf( &msg[pos], "%04i: ", i ); + + pos += sprintf( &msg[pos], "0x%02X ", (unsigned char) buf[i] ); + + if ( i % 16 == 15 ) { + pos += sprintf( &msg[pos], "\n" ); + purple_debug_info( MXIT_PLUGIN_ID, msg ); + pos = 0; + } + else if ( i % 16 == 7 ) + pos += sprintf( &msg[pos], " " ); + } + + if ( pos > 0 ) { + pos += sprintf( &msg[pos], "\n" ); + purple_debug_info( MXIT_PLUGIN_ID, msg ); + pos = 0; + } +} +#endif + + +/*------------------------------------------------------------------------ + * Adds a link to a message + * + * @param mx The Markup message object + * @param linkname This is the what will be returned when the link gets clicked + * @param displayname This is the name for the link which will be displayed in the UI + */ +void mxit_add_html_link( struct RXMsgData* mx, const char* linkname, const char* displayname ) +{ +#ifdef MXIT_LINK_CLICK + char retstr[256]; + gchar* retstr64; + char link[256]; + int len; + + len = g_snprintf( retstr, sizeof( retstr ), "%s|%s|%s|%s|%s", MXIT_LINK_KEY, purple_account_get_username( mx->session->acc ), + purple_account_get_protocol_id( mx->session->acc ), mx->from, linkname ); + retstr64 = purple_base64_encode( (const unsigned char*) retstr, len ); + g_snprintf( link, sizeof( link ), "%s%s", MXIT_LINK_PREFIX, retstr64 ); + g_free( retstr64 ); + + g_string_append_printf( mx->msg, "<a href=\"%s\">%s</a>", link, displayname ); +#else + g_string_append_printf( mx->msg, "<b>%s</b>", linkname ); +#endif +} + + +/*------------------------------------------------------------------------ + * Extract an ASN.1 formatted length field from the data. + * + * @param data The source data + * @param size The extracted length + * @return The number of bytes extracted + */ +static unsigned int asn_getlength( const char* data, int* size ) +{ + unsigned int len = 0; + unsigned char bytes; + unsigned char byte; + int i; + + /* first byte specifies the number of bytes in the length */ + bytes = ( data[0] & ~0x80 ); + if ( bytes > sizeof( unsigned int ) ) { + /* file too big! */ + return -1; + } + data++; + + /* parse out the actual length */ + for ( i = 0; i < bytes; i++ ) { + byte = data[i]; + len <<= 8; + len += byte; + } + + *size = len; + return bytes + 1; +} + + +/*------------------------------------------------------------------------ + * Extract an ASN.1 formatted UTF-8 string field from the data. + * + * @param data The source data + * @param type Expected type of string + * @param utf8 The extracted string. Must be deallocated by caller. + * @return The number of bytes extracted + */ +static int asn_getUtf8( const char* data, char type, char** utf8 ) +{ + int len; + + /* validate the field type [1 byte] */ + if ( data[0] != type ) { + /* this is not a utf-8 string! */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid UTF-8 encoded string in ASN data (0x%02X)\n", (unsigned char) data[0] ); + return -1; + } + + len = data[1]; /* length field [1 bytes] */ + *utf8 = g_malloc( len + 1 ); + memcpy( *utf8, &data[2], len ); /* data field */ + (*utf8)[len] = '\0'; + + return ( len + 2 ); +} + + +/*------------------------------------------------------------------------ + * Free data associated with a Markup message object. + * + * @param mx The Markup message object + */ +static void free_markupdata( struct RXMsgData* mx ) +{ + if ( mx ) { + if ( mx->msg ) + g_string_free( mx->msg, TRUE ); + if ( mx->from ) + g_free( mx->from ); + g_free( mx ); + } +} + + +/*------------------------------------------------------------------------ + * Split the message into smaller messages and send them one at a time + * to pidgin to be displayed on the UI + * + * @param mx The received message object + */ +static void mxit_show_split_message( struct RXMsgData* mx ) +{ + const char* cont = "<font color=\"#999999\">continuing...</font>\n"; + GString* msg = NULL; + char* ch = NULL; + int pos = 0; + int start = 0; + int l_nl = 0; + int l_sp = 0; + int l_gt = 0; + int stop = 0; + int tags = 0; + int segs = 0; + gboolean intag = FALSE; + + /* + * awful hack to work around the awful hack in pidgin to work around GtkIMHtml's + * inefficient rendering of messages with lots of formatting changes. + * (reference: see the function pidgin_conv_write_conv() in gtkconv.c) the issue + * is that when you have more than 100 '<' characters in the message passed to + * pidgin, none of the markup (including links) are rendered and thus just dump + * all the text as is to the conversation window. this message dump is very + * confusing and makes it totally unusable. to work around this we will count + * the amount of tags and if its more than the pidgin threshold, we will just + * break the message up into smaller parts and send them seperately to pidgin. + * to the user it will look like multiple messages, but at least he will be able + * to use and understand it. + */ + + ch = mx->msg->str; + pos = start; + while ( ch[pos] ) { + + if ( ch[pos] == '<' ) { + tags++; + intag = TRUE; + } + else if ( ch[pos] == '\n' ) { + l_nl = pos; + } + else if ( ch[pos] == '>' ) { + l_gt = pos; + intag = FALSE; + } + else if ( ch[pos] == ' ' ) { + /* ignore spaces inside tags */ + if ( !intag ) + l_sp = pos; + } + else if ( ( ch[pos] == 'w' ) && ( pos + 4 < mx->msg->len ) && ( memcmp( &ch[pos], "www.", 4 ) == 0 ) ) { + tags += 2; + } + else if ( ( ch[pos] == 'h' ) && ( pos + 8 < mx->msg->len ) && ( memcmp( &ch[pos], "http://", 7 ) == 0 ) ) { + tags += 2; + } + + if ( tags > MXIT_MAX_MSG_TAGS ) { + /* we have reached the maximum amount of tags pidgin (gtk) can handle per message. + so its time to send what we have and then start building a new message */ + + /* now find the right place to break the message */ + if ( l_nl > start ) { + /* break at last '\n' char */ + stop = l_nl; + ch[stop] = '\0'; + msg = g_string_new( &ch[start] ); + ch[stop] = '\n'; + } + else if ( l_sp > start ) { + /* break at last ' ' char */ + stop = l_sp; + ch[stop] = '\0'; + msg = g_string_new( &ch[start] ); + ch[stop] = ' '; + } + else { + /* break at the last '>' char */ + char t; + stop = l_gt + 1; + t = ch[stop]; + ch[stop] = '\0'; + msg = g_string_new( &ch[start] ); + ch[stop] = t; + stop--; + } + + /* build the string */ + if ( segs ) + g_string_prepend( msg, cont ); + + /* push message to pidgin */ + serv_got_im( mx->session->con, mx->from, msg->str, mx->flags, mx->timestamp ); + g_string_free( msg, TRUE ); + msg = NULL; + + tags = 0; + segs++; + start = stop + 1; + } + + pos++; + } + + if ( start != pos ) { + /* send the last part of the message */ + + /* build the string */ + ch[pos] = '\0'; + msg = g_string_new( &ch[start] ); + ch[pos] = '\n'; + if ( segs ) + g_string_prepend( msg, cont ); + + /* push message to pidgin */ + serv_got_im( mx->session->con, mx->from, msg->str, mx->flags, mx->timestamp ); + g_string_free( msg, TRUE ); + msg = NULL; + } +} + + +/*------------------------------------------------------------------------ + * Insert custom emoticons and inline images into the message (if there + * are any), then give the message to the UI to display to the user. + * + * @param mx The received message object + */ +void mxit_show_message( struct RXMsgData* mx ) +{ + char* pos; + int start; + unsigned int end; + int emo_ofs; + char ii[128]; + char tag[64]; + int* img_id; + + if ( mx->got_img ) { + /* search and replace all emoticon tags with proper image tags */ + + while ( ( pos = strstr( mx->msg->str, MXIT_II_TAG ) ) != NULL ) { + start = pos - mx->msg->str; /* offset at which MXIT_II_TAG starts */ + emo_ofs = start + strlen( MXIT_II_TAG ); /* offset at which EMO's ID starts */ + end = emo_ofs + 1; /* offset at which MXIT_II_TAG ends */ + + while ( ( end < mx->msg->len ) && ( mx->msg->str[end] != '>' ) ) + end++; + + if ( end == mx->msg->len ) /* end of emoticon tag not found */ + break; + + memset( ii, 0x00, sizeof( ii ) ); + memcpy( ii, &mx->msg->str[emo_ofs], end - emo_ofs ); + + /* remove inline image tag */ + g_string_erase( mx->msg, start, ( end - start ) + 1 ); + + /* find the image entry */ + img_id = (int*) g_hash_table_lookup( mx->session->iimages, ii ); + if ( !img_id ) { + /* inline image not found, so we will just skip it */ + purple_debug_error( MXIT_PLUGIN_ID, "inline image NOT found (%s)\n", ii ); + } + else { + /* insert img tag */ + g_snprintf( tag, sizeof( tag ), "<img id=\"%i\">", *img_id ); + g_string_insert( mx->msg, start, tag ); + } + } + } + +#ifdef MXIT_DEBUG_MARKUP + purple_debug_info( MXIT_PLUGIN_ID, "Markup RX (converted): '%s'\n", mx->msg->str ); +#endif + + if ( mx->processed ) { + /* this message has already been taken care of, so just ignore it here */ + } + else if ( mx->chatid < 0 ) { + /* normal chat message */ + //serv_got_im( mx->session->con, mx->from, mx->msg->str, mx->flags, mx->timestamp ); + mxit_show_split_message( mx ); + } + else { + /* this is a multimx message */ + serv_got_chat_in( mx->session->con, mx->chatid, mx->from, mx->flags, mx->msg->str, mx->timestamp); + } + + /* freeup resource */ + free_markupdata( mx ); +} + + +/*------------------------------------------------------------------------ + * Extract the custom emoticon ID from the message. + * + * @param message The input data + * @param emid The extracted emoticon ID + */ +static void parse_emoticon_str( const char* message, char* emid ) +{ + int i; + + for ( i = 0; ( message[i] != '\0' && message[i] != '}' && i < MXIT_MAX_EMO_ID ); i++ ) { + emid[i] = message[i]; + } + + if ( message[i] == '\0' ) { + /* end of message reached, ignore the tag */ + emid[0] = '\0'; + } + else if ( i == MXIT_MAX_EMO_ID ) { + /* invalid tag length, ignore the tag */ + emid[0] = '\0'; + } + else + emid[i] = '\0'; +} + + +/*------------------------------------------------------------------------ + * Callback function invoked when a custom emoticon request to the WAP site completes. + * + * @param url_data + * @param user_data The Markup message object + * @param url_text The data returned from the WAP site + * @param len The length of the data returned + * @param error_message Descriptive error message + */ +static void emoticon_returned( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message ) +{ + struct RXMsgData* mx = (struct RXMsgData*) user_data; + const char* data = url_text; + unsigned int pos = 0; + char emo[16]; + int id; + char* str; + int em_size = 0; + char* em_data = NULL; + char* em_id = NULL; + int* intptr = NULL; + int res; + +#ifdef MXIT_DEBUG_EMO + purple_debug_info( MXIT_PLUGIN_ID, "emoticon_returned\n" ); +#endif + + if ( !url_text ) { + /* no reply from the WAP site */ + purple_debug_error( MXIT_PLUGIN_ID, "Error contacting the MXit WAP site. Please try again later (emoticon).\n" ); + goto done; + } + +#ifdef MXIT_DEBUG_EMO + hex_dump( data, len ); +#endif + + /* parse out the emoticon */ + pos = 0; + + /* validate the binary data received from the wapsite */ + if ( memcmp( MXIT_FRAME_MAGIC, &data[pos], strlen( MXIT_FRAME_MAGIC ) ) != 0 ) { + /* bad data, magic constant is wrong */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad magic)\n" ); + goto done; + } + pos += strlen( MXIT_FRAME_MAGIC ); + + /* validate the image frame desc byte */ + if ( data[pos] != '\x6F' ) { + /* bad frame desc */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad frame desc)\n" ); + goto done; + } + pos++; + + /* get the data length */ + res = asn_getlength( &data[pos], &em_size ); + if ( res <= 0 ) { + /* bad frame length */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad frame length)\n" ); + goto done; + } + pos += res; +#ifdef MXIT_DEBUG_EMO + purple_debug_info( MXIT_PLUGIN_ID, "read the length '%i'\n", em_size ); +#endif + + /* utf-8 (emoticon name) */ + res = asn_getUtf8( &data[pos], 0x0C, &str ); + if ( res <= 0 ) { + /* bad utf-8 string */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad name string)\n" ); + goto done; + } + pos += res; +#ifdef MXIT_DEBUG_EMO + purple_debug_info( MXIT_PLUGIN_ID, "read the string '%s'\n", str ); +#endif + g_free( str ); + str = NULL; + + /* utf-8 (emoticon shortcut) */ + res = asn_getUtf8( &data[pos], 0x81, &str ); + if ( res <= 0 ) { + /* bad utf-8 string */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad shortcut string)\n" ); + goto done; + } + pos += res; +#ifdef MXIT_DEBUG_EMO + purple_debug_info( MXIT_PLUGIN_ID, "read the string '%s'\n", str ); +#endif + em_id = str; + + /* validate the image data type */ + if ( data[pos] != '\x82' ) { + /* bad frame desc */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad data type)\n" ); + g_free( em_id ); + goto done; + } + pos++; + + /* get the data length */ + res = asn_getlength( &data[pos], &em_size ); + if ( res <= 0 ) { + /* bad frame length */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad data length)\n" ); + g_free( em_id ); + goto done; + } + pos += res; +#ifdef MXIT_DEBUG_EMO + purple_debug_info( MXIT_PLUGIN_ID, "read the length '%i'\n", em_size ); +#endif + + if ( g_hash_table_lookup( mx->session->iimages, em_id ) ) { + /* emoticon found in the table, so ignore this one */ + goto done; + } + + /* make a copy of the data */ + em_data = g_malloc( em_size ); + memcpy( em_data, &data[pos], em_size ); + + /* strip the mxit markup tags from the emoticon id */ + if ( ( em_id[0] == '.' ) && ( em_id[1] == '{' ) ) { + parse_emoticon_str( &em_id[2], emo ); + strcpy( em_id, emo ); + } + + /* we now have the emoticon, store it in the imagestore */ + id = purple_imgstore_add_with_id( em_data, em_size, NULL ); + + /* map the mxit emoticon id to purple image id */ + intptr = g_malloc( sizeof( int ) ); + *intptr = id; + g_hash_table_insert( mx->session->iimages, em_id, intptr ); + + mx->flags |= PURPLE_MESSAGE_IMAGES; +done: + mx->img_count--; + if ( ( mx->img_count == 0 ) && ( mx->converted ) ) { + /* + * this was the last outstanding emoticon for this message, + * so we can now display it to the user. + */ + mxit_show_message( mx ); + } +} + + +/*------------------------------------------------------------------------ + * Send a request to the MXit WAP site to download the specified emoticon. + * + * @param mx The Markup message object + * @param id The ID for the emoticon + */ +static void emoticon_request( struct RXMsgData* mx, const char* id ) +{ + PurpleUtilFetchUrlData* url_data; + const char* wapserver; + char* url; + + purple_debug_info( MXIT_PLUGIN_ID, "sending request for emoticon '%s'\n", id ); + + wapserver = purple_account_get_string( mx->session->acc, MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE ); + + /* reference: "libpurple/util.h" */ + url = g_strdup_printf( "%s/res/?type=emo&mlh=%i&sc=%s&ts=%li", wapserver, MXIT_EMOTICON_SIZE, id, time( NULL ) ); + url_data = purple_util_fetch_url_request( url, TRUE, NULL, TRUE, NULL, FALSE, emoticon_returned, mx ); + g_free( url ); +} + + +/*------------------------------------------------------------------------ + * Parse a Vibe command. + * + * @param mx The Markup message object + * @param message The message text (which contains the vibe) + * @return id The length of the message to skip + */ +static int mxit_parse_vibe( struct RXMsgData* mx, const char* message ) +{ + int vibeid; + + vibeid = message[2] - '0'; + + purple_debug_info( MXIT_PLUGIN_ID, "Vibe received (%i)\n", vibeid ); + + if ( vibeid > ( ARRAY_SIZE( vibes ) - 1 ) ) { + purple_debug_warning( MXIT_PLUGIN_ID, "Unsupported vibe received (%i)\n", vibeid ); + /* unsupported vibe */ + return 0; + } + + g_string_append_printf( mx->msg, "<font color=\"%s\"><i>%s Vibe...</i></font>", MXIT_VIBE_MSG_COLOR, vibes[vibeid] ); + return 2; +} + + +/*------------------------------------------------------------------------ + * Extract the nickname from a chatroom message and display it nicely in + * libPurple-style (HTML) markup. + * + * @param mx The received message data object + * @param message The message text + * @return The length of the message to skip + */ +static int mxit_extract_chatroom_nick( struct RXMsgData* mx, char* message, int len ) +{ + int i; + + if ( message[0] == '<' ) { + /* + * The message MIGHT contains an embedded nickname. But we can't + * be sure unless we find the end-of-nickname sequence: (>\n) + * Search for it.... + */ + gboolean found = FALSE; + gchar* nickname; + + for ( i = 1; i < len; i++ ) { + if ( ( message[i] == '\n' ) && ( message[i-1] == '>' ) ) { + found = TRUE; + message[i-1] = '\0'; /* loose the '>' */ + i++; /* and skip the new-line */ + break; + } + } + + if ( found ) { + /* + * The message definitely had an embedded nickname - generate a marked-up + * message to be displayed. + */ + nickname = g_markup_escape_text( &message[1], -1 ); + + /* add nickname within some BOLD markup to the new converted message */ + g_string_append_printf( mx->msg, "<b>%s:</b> ", nickname ); + + /* free up the resources */ + g_free( nickname ); + + return i; + } + } + + return 0; +} + + + +/*------------------------------------------------------------------------ + * Convert a message containing MXit protocol markup to libPurple-style (HTML) markup. + * + * @param mx The received message data object + * @param message The message text + * @param len The length of the message + */ +void mxit_parse_markup( struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags ) +{ + char tmpstr1[128]; + char* ch; + int i = 0; + + /* tags */ + gboolean tag_bold = FALSE; + gboolean tag_under = FALSE; + gboolean tag_italic = FALSE; + +#ifdef MXIT_DEBUG_MARKUP + purple_debug_info( MXIT_PLUGIN_ID, "Markup RX (original): '%s'\n", message ); +#endif + + + /* + * supported MXit markup: + * '*' bold + * '_' underline + * '/' italics + * '$' highlight text + * '.+' inc font size + * '.-' dec font size + * '#XXXXXX' foreground color + * '.{XX}' custom emoticon + * '\' escape the following character + * '::' MXit commands + */ + + + if ( is_mxit_chatroom_contact( mx->session, mx->from ) ) { + /* chatroom message, so we need to extract and skip the sender's nickname + * which is embedded inside the message */ + i = mxit_extract_chatroom_nick( mx, message, len ); + } + + /* run through the message and check for custom emoticons and markup */ + for ( ; i < len; i++ ) { + switch ( message[i] ) { + + + /* mxit markup parsing */ + case '*' : + if ( !( msgflags & CP_MSG_MARKUP ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + + /* bold markup */ + if ( !tag_bold ) + g_string_append( mx->msg, "<b>" ); + else + g_string_append( mx->msg, "</b>" ); + tag_bold = !tag_bold; + break; + case '_' : + if ( !( msgflags & CP_MSG_MARKUP ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + + /* underscore markup */ + if ( !tag_under ) + g_string_append( mx->msg, "<u>" ); + else + g_string_append( mx->msg, "</u>" ); + tag_under = !tag_under; + break; + case '/' : + if ( !( msgflags & CP_MSG_MARKUP ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + + /* italics markup */ + if ( !tag_italic ) + g_string_append( mx->msg, "<i>" ); + else + g_string_append( mx->msg, "</i>" ); + tag_italic = !tag_italic; + break; + case '$' : + if ( !( msgflags & CP_MSG_MARKUP ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + else if ( i + 1 >= len ) { + /* message too short for complete link */ + g_string_append_c( mx->msg, '$' ); + break; + } + + /* find the end tag */ + ch = strstr( &message[i + 1], "$" ); + if ( ch ) { + /* end found */ + *ch = '\0'; + mxit_add_html_link( mx, &message[i + 1], &message[i + 1] ); + *ch = '$'; + i += ( ch - &message[i + 1] ) + 1; + } + else { + g_string_append_c( mx->msg, message[i] ); + } + /* highlight text */ + break; + case '#' : + if ( !( msgflags & CP_MSG_MARKUP ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + else if ( i + COLORCODE_LEN >= len ) { + /* message too short for complete colour code */ + g_string_append_c( mx->msg, '#' ); + break; + } + + /* foreground (text) color */ + memcpy( tmpstr1, &message[i + 1], COLORCODE_LEN ); + tmpstr1[ COLORCODE_LEN ] = '\0'; /* terminate string */ + if ( strcmp( tmpstr1, "??????" ) == 0 ) { + /* need to reset the font */ + g_string_append( mx->msg, "</font>" ); + i += COLORCODE_LEN; + } + else if ( strspn( tmpstr1, "0123456789abcdefABCDEF") == COLORCODE_LEN ) { + /* definitely a numeric colour code */ + g_string_append_printf( mx->msg, "<font color=\"#%s\">", tmpstr1 ); + i += COLORCODE_LEN; + } + else { + /* not valid colour markup */ + g_string_append_c( mx->msg, '#' ); + } + break; + case '.' : + if ( !( msgflags & CP_MSG_EMOTICON ) ) { + g_string_append_c( mx->msg, message[i] ); + break; + } + else if ( i + 1 >= len ) { + /* message too short */ + g_string_append_c( mx->msg, '.' ); + break; + } + + switch ( message[i+1] ) { + case '+' : + /* increment text size */ + g_string_append( mx->msg, "<font size=\"+1\">" ); + i++; + break; + case '-' : + /* decrement text size */ + g_string_append( mx->msg, "<font size=\"-1\">" ); + i++; + break; + case '{' : + /* custom emoticon */ + if ( i + 2 >= len ) { + /* message too short */ + g_string_append_c( mx->msg, '.' ); + break; + } + + parse_emoticon_str( &message[i+2], tmpstr1 ); + if ( tmpstr1[0] != '\0' ) { + mx->got_img = TRUE; + + if ( g_hash_table_lookup( mx->session->iimages, tmpstr1 ) ) { + /* emoticon found in the cache, so we do not have to request it from the WAPsite */ + } + else { + /* request emoticon from the WAPsite */ + mx->img_count++; + emoticon_request( mx, tmpstr1 ); + } + + g_string_append_printf( mx->msg, MXIT_II_TAG"%s>", tmpstr1 ); + i += strlen( tmpstr1 ) + 2; + } + else + g_string_append_c( mx->msg, '.' ); + + break; + default : + g_string_append_c( mx->msg, '.' ); + break; + } + break; + case '\\' : + if ( i + 1 >= len ) { + /* message too short for an escaped character */ + g_string_append_c( mx->msg, '\\' ); + } + else { + /* ignore the next character, because its been escaped */ + g_string_append_c( mx->msg, message[i + 1] ); + i++; + } + break; + + + /* command parsing */ + case ':' : + if ( i + 1 >= len ) { + /* message too short */ + g_string_append_c( mx->msg, ':' ); + break; + } + + if ( message[i+1] == '@' ) { + /* this is a vibe! */ + int size; + + if ( i + 2 >= len ) { + /* message too short */ + g_string_append_c( mx->msg, message[i] ); + break; + } + + size = mxit_parse_vibe( mx, &message[i] ); + if ( size == 0 ) + g_string_append_c( mx->msg, message[i] ); + else + i += size; + } + else if ( msgtype != CP_MSGTYPE_COMMAND ) { + /* this is not a command message */ + g_string_append_c( mx->msg, message[i] ); + } + else if ( message[i+1] == ':' ) { + /* parse out the command */ + int size; + + size = mxit_parse_command( mx, &message[i] ); + if ( size == 0 ) + g_string_append_c( mx->msg, ':' ); + else + i += size; + } + else { + g_string_append_c( mx->msg, ':' ); + } + break; + + + /* these aren't MXit markup, but are interpreted by libPurple */ + case '<' : + g_string_append( mx->msg, "<" ); + 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: <b>...</b> + * Italics: <i>...</i> + * Underline: <u>...</u> + * Strikethrough: <s>...</s> (NO MXIT SUPPORT) + * Font size: <font size="">...</font> + * Font type: <font face="">...</font> (NO MXIT SUPPORT) + * Font colour: <font color=#">...</font> + * Links: <a href="">...</a> + * Newline: <br> + * Inline image: <IMG ID=""> + * The following characters are also encoded: + * & " < > + */ + + /* new message data */ + mx = g_string_sized_new( len ); + + /* run through the message and check for HTML markup */ + for ( i = 0; i < len; i++ ) { + + switch ( message[i] ) { + case '<' : + if ( purple_str_has_prefix( &message[i], "<b>" ) || purple_str_has_prefix( &message[i], "</b>" ) ) { + /* bold */ + g_string_append_c( mx, '*' ); + } + else if ( purple_str_has_prefix( &message[i], "<i>" ) || purple_str_has_prefix( &message[i], "</i>" ) ) { + /* italics */ + g_string_append_c( mx, '/' ); + } + else if ( purple_str_has_prefix( &message[i], "<u>" ) || purple_str_has_prefix( &message[i], "</u>" ) ) { + /* underline */ + g_string_append_c( mx, '_' ); + } + else if ( purple_str_has_prefix( &message[i], "<br>" ) ) { + /* newline */ + g_string_append_c( mx, '\n' ); + } + else if ( purple_str_has_prefix( &message[i], "<font size=" ) ) { + /* font size */ + tag = g_new0( struct tag, 1 ); + tag->type = MXIT_TAG_SIZE; + tagstack = g_list_prepend( tagstack, tag ); + // TODO: implement size control + } + else if ( purple_str_has_prefix( &message[i], "<font color=" ) ) { + /* font colour */ + tag = g_new0( struct tag, 1 ); + tag->type = MXIT_TAG_COLOR; + tagstack = g_list_append( tagstack, tag ); + memset( color, 0x00, sizeof( color ) ); + memcpy( color, &message[i + 13], 7 ); + g_string_append( mx, color ); + } + else if ( purple_str_has_prefix( &message[i], "</font>" ) ) { + /* end of font tag */ + entry = g_list_last( tagstack ); + if ( entry ) { + tag = entry->data; + if ( tag->type == MXIT_TAG_COLOR ) { + /* font color reset */ + g_string_append( mx, "#??????" ); + } + else if ( tag->type == MXIT_TAG_SIZE ) { + /* font size */ + // TODO: implement size control + } + tagstack = g_list_remove( tagstack, tag ); + g_free( tag ); + } + } + else if ( purple_str_has_prefix( &message[i], "<IMG ID=" ) ) { + /* inline image */ + int imgid; + + if ( sscanf( &message[i+9], "%i", &imgid ) ) { + inline_image_add( mx, imgid ); + *msgtype = CP_MSGTYPE_COMMAND; /* inline image must be sent as a MXit command */ + } + } + + /* skip to end of tag ('>') */ + for ( i++; ( i < len ) && ( message[i] != '>' ) ; i++ ); + + break; + + case '*' : /* MXit bold */ + case '_' : /* MXit underline */ + case '/' : /* MXit italic */ + case '#' : /* MXit font color */ + case '$' : /* MXit highlight text */ + case '\\' : /* MXit escape backslash */ + g_string_append( mx, "\\" ); /* escape character */ + g_string_append_c( mx, message[i] ); /* character to escape */ + break; + + default: + g_string_append_c( mx, message[i] ); + break; + } + } + + /* unescape HTML entities to their literal characters (reference: "libpurple/utils.h") */ + reply = purple_unescape_html( mx->str ); + + g_string_free( mx, TRUE ); + +#ifdef MXIT_DEBUG_MARKUP + purple_debug_info( MXIT_PLUGIN_ID, "Markup TX (converted): '%s'\n", reply ); +#endif + + return reply; +} + + +/*------------------------------------------------------------------------ + * Free an emoticon entry. + * + * @param key MXit emoticon ID + * @param value Imagestore ID for emoticon + * @param user_data NULL (unused) + * @return TRUE + */ +static gboolean emoticon_entry_free( gpointer key, gpointer value, gpointer user_data ) +{ + int* imgid = value; + + /* key is a string */ + g_free( key ); + + /* value is 'id' in imagestore */ + purple_imgstore_unref_by_id( *imgid ); + g_free( value ); + + return TRUE; +} + + +/*------------------------------------------------------------------------ + * Free all entries in the emoticon cache. + * + * @param session The MXit session object + */ +void mxit_free_emoticon_cache( struct MXitSession* session ) +{ + g_hash_table_foreach_remove( session->iimages, emoticon_entry_free, NULL ); + g_hash_table_destroy ( session->iimages ); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/markup.h Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,40 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- convert between MXit and libPurple markup -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_MARKUP_H_ +#define _MXIT_MARKUP_H_ + +#define MXIT_II_TAG "<MXII=" /* inline image placeholder string */ + + +void mxit_parse_markup( struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags ); +char* mxit_convert_markup_tx( const char* message, int* msgtype ); +void mxit_add_html_link( struct RXMsgData* mx, const char* linkname, const char* displayname ); +void mxit_show_message( struct RXMsgData* mx ); + +void mxit_free_emoticon_cache( struct MXitSession* session ); + + +#endif /* _MXIT_MARKUP_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/multimx.c Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,597 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MultiMx GroupChat -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <string.h> +#include <errno.h> + +#include "purple.h" +#include "prpl.h" + +#include "protocol.h" +#include "mxit.h" +#include "multimx.h" +#include "markup.h" + + +#if 0 +static void multimx_dump(struct multimx* multimx) +{ + purple_debug_info(MXIT_PLUGIN_ID, "MultiMX:\n"); + purple_debug_info(MXIT_PLUGIN_ID, " Chat ID: %i\n", multimx->chatid); + purple_debug_info(MXIT_PLUGIN_ID, " Username: %s\n", multimx->roomid); + purple_debug_info(MXIT_PLUGIN_ID, " Alias: %s\n", multimx->roomname); + purple_debug_info(MXIT_PLUGIN_ID, " State: %i\n", multimx->state); +} +#endif + + +/*------------------------------------------------------------------------ + * Find a MultiMx session based on libpurple chatID. + * + * @param session The MXit session object + * @param id The libpurple group-chat ID + * @return The MultiMX room object (or NULL if not found) + */ +static struct multimx* find_room_by_id(struct MXitSession* session, int id) +{ + GList* x = session->rooms; + + while (x != NULL) { + struct multimx* multimx = (struct multimx *) x->data; + + if (multimx->chatid == id) + return multimx; + + x = g_list_next(x); + } + + return NULL; +} + + +/*------------------------------------------------------------------------ + * Find a MultiMx session based on Alias + * + * @param session The MXit session object + * @param roomname The UI room-name + * @return The MultiMX room object (or NULL if not found) + */ +static struct multimx* find_room_by_alias(struct MXitSession* session, const char* roomname) +{ + GList* x = session->rooms; + + while (x != NULL) { + struct multimx* multimx = (struct multimx *) x->data; + + if (!strcmp(multimx->roomname, roomname)) + return multimx; + + x = g_list_next(x); + } + + return NULL; +} + + +/*------------------------------------------------------------------------ + * Find a MultiMx session based on Username (MXit RoomId) + * + * @param session The MXit session object + * @param username The MXit RoomID (MultiMX contact username) + * @return The MultiMX room object (or NULL if not found) + */ +static struct multimx* find_room_by_username(struct MXitSession* session, const char* username) +{ + GList* x = session->rooms; + + while (x != NULL) { + struct multimx* multimx = (struct multimx *) x->data; + + if (!strcmp(multimx->roomid, username)) + return multimx; + + x = g_list_next(x); + } + + return NULL; +} + + +/*------------------------------------------------------------------------ + * Create a GroupChat room, and add to list of rooms. + * + * @param session The MXit session object + * @param roomid The MXit RoomID (MultiMX contact username) + * @param roomname The UI room-name + * @param state The initial state of the room (see multimx.h) + * @return The MultiMX room object + */ +static struct multimx* room_create(struct MXitSession* session, const char* roomid, const char* roomname, short state) +{ + struct multimx* multimx = NULL; + static int groupchatID = 1; + + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat create - roomid='%s' roomname='%s'\n", roomid, roomname); + + /* Create a new GroupChat */ + multimx = g_new0(struct multimx, 1); + + /* Initialize groupchat */ + g_strlcpy(multimx->roomid, roomid, sizeof(multimx->roomid)); + g_strlcpy(multimx->roomname, roomname, sizeof(multimx->roomname)); + multimx->chatid = groupchatID++; + multimx->state = state; + + /* Add to GroupChat list */ + session->rooms = g_list_append(session->rooms, multimx); + + return multimx; +} + + +/*------------------------------------------------------------------------ + * Free the Groupchat room. + * + * @param session The MXit session object + * @param multimx The MultiMX room object to deallocate + */ +static void room_remove(struct MXitSession* session, struct multimx* multimx) +{ + /* Remove from GroupChat list */ + session->rooms = g_list_remove(session->rooms, multimx); + + /* Deallocate it */ + free (multimx); + multimx = NULL; +} + + +/*------------------------------------------------------------------------ + * Another user has join the GroupChat, add them to the member-list. + * + * @param session The MXit session object + * @param multimx The MultiMX room object + * @param nickname The nickname of the user who joined the room + */ +static void member_added(struct MXitSession* session, struct multimx* multimx, const char* nickname) +{ + PurpleConversation *convo; + + purple_debug_info(MXIT_PLUGIN_ID, "member_added: '%s'\n", nickname); + + convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc); + if (convo == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname); + return; + } + + purple_conv_chat_add_user(PURPLE_CONV_CHAT(convo), nickname, NULL, PURPLE_CBFLAGS_NONE, TRUE); +} + + +/*------------------------------------------------------------------------ + * Another user has left the GroupChat, remove them from the member-list. + * + * @param session The MXit session object + * @param multimx The MultiMX room object + * @param nickname The nickname of the user who left the room + */ +static void member_removed(struct MXitSession* session, struct multimx* multimx, const char* nickname) +{ + PurpleConversation *convo; + + purple_debug_info(MXIT_PLUGIN_ID, "member_removed: '%s'\n", nickname); + + convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc); + if (convo == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname); + return; + } + + purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo), nickname, NULL); +} + + +/*------------------------------------------------------------------------ + * Update the full GroupChat member list. + * + * @param session The MXit session object + * @param multimx The MultiMX room object + * @param data The nicknames of the users in the room (separated by \n) + */ +static void member_update(struct MXitSession* session, struct multimx* multimx, char* data) +{ + PurpleConversation *convo; + gchar** userlist; + int i = 0; + + purple_debug_info(MXIT_PLUGIN_ID, "member_update: '%s'\n", data); + + convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc); + if (convo == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname); + return; + } + + /* Clear list */ + purple_conv_chat_clear_users(PURPLE_CONV_CHAT(convo)); + + /* Add each member */ + data = g_strstrip(data); /* string leading & trailing whitespace */ + userlist = g_strsplit(data, "\n", 0); /* tokenize string */ + while (userlist[i] != NULL) { + purple_debug_info(MXIT_PLUGIN_ID, "member_update - adding: '%s'\n", userlist[i]); + purple_conv_chat_add_user(PURPLE_CONV_CHAT(convo), userlist[i], NULL, PURPLE_CBFLAGS_NONE, FALSE); + i++; + } + g_strfreev(userlist); +} + + +/* ------------------------------------------------------------------------------------------------- + * Calls from MXit Protocol layer + * ------------------------------------------------------------------------------------------------- */ + +/*------------------------------------------------------------------------ + * Received a Subscription Request to a MultiMX room. + * + * @param session The MXit session object + * @param contact The invited MultiMX room's contact information + * @param creator The nickname of the room's creator / invitor + */ +void multimx_invite(struct MXitSession* session, struct contact* contact, const char* creator) +{ + GHashTable *components; + struct multimx* multimx = NULL; + + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat invite to '%s' by '%s'\n", contact->alias, creator); + + /* Create a new room */ + multimx = room_create(session, contact->username, contact->alias, STATE_INVITED); + + components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_insert(components, g_strdup("room"), g_strdup(contact->alias)); + + /* Call libpurple - will trigger either 'mxit_chat_join' or 'mxit_chat_reject' */ + serv_got_chat_invite(session->con, contact->alias, creator, NULL, components); +} + + +/*------------------------------------------------------------------------ + * MultiMX room has been added to the roster. + * + * @param session The MXit session object + * @param contact The MultiMX room's contact information + */ +void multimx_created(struct MXitSession* session, struct contact* contact) +{ + PurpleConnection *gc = session->con; + struct multimx* multimx = NULL; + + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat '%s' created as '%s'\n", contact->alias, contact->username); + + /* Find matching MultiMX group */ + multimx = find_room_by_username(session, contact->username); + if (multimx == NULL) { + multimx = room_create(session, contact->username, contact->alias, TRUE); + } + else if (multimx->state == STATE_INVITED) { + /* After successfully accepting an invitation */ + multimx->state = STATE_JOINED; + } + + /* Call libpurple - will trigger 'mxit_chat_join' */ + serv_got_joined_chat(gc, multimx->chatid, multimx->roomname); + + /* Send ".list" command to GroupChat server to retrieve current member-list */ + mxit_send_message(session, multimx->roomid, ".list", FALSE); +} + + +/*------------------------------------------------------------------------ + * Is this username a MultiMX contact? + * + * @param session The MXit session object + * @param username The username of the contact + * @return TRUE if this contacts matches the RoomID of a MultiMX room. + */ +gboolean is_multimx_contact(struct MXitSession* session, const char* username) +{ + /* Check for username in list of open rooms */ + return (find_room_by_username(session, username) != NULL); +} + + +/*------------------------------------------------------------------------ + * Received a message from a MultiMX room. + * + */ +void multimx_message_received(struct RXMsgData* mx, char* msg, int msglen, short msgtype, int msgflags) +{ + struct multimx* multimx = NULL; + + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat message received: %s\n", msg); + + /* Find matching multimx group */ + multimx = find_room_by_username(mx->session, mx->from); + if (multimx == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Groupchat '%s' not found\n", mx->from); + return; + } + + /* Determine if system message or a message from a contact */ + if (msg[0] == '<') { + /* Message contains embedded nickname - must be from contact */ + unsigned int i; + + for (i = 1; i < strlen(msg); i++) { /* search for end of nickname */ + if (msg[i] == '>') { + msg[i] = '\0'; + g_free(mx->from); + mx->from = g_strdup(&msg[1]); + msg = &msg[i+2]; /* skip '>' and newline */ + break; + } + } + + /* now do markup processing on the message */ + mx->chatid = multimx->chatid; + mxit_parse_markup(mx, msg, strlen(msg), msgtype, msgflags); + } + else { + /* Must be a service message */ + char* ofs; + + /* Determine if somebody has joined or left - update member-list */ + if ((ofs = strstr(msg, " has joined")) != NULL) { + /* Somebody has joined */ + *ofs = '\0'; + member_added(mx->session, multimx, msg); + mx->processed = TRUE; + } + else if ((ofs = strstr(msg, " has left")) != NULL) { + /* Somebody has left */ + *ofs = '\0'; + member_removed(mx->session, multimx, msg); + mx->processed = TRUE; + } + else if (g_str_has_prefix(msg, "The following users are in this MultiMx:") == TRUE) { + member_update(mx->session, multimx, msg + strlen("The following users are in this MultiMx:") + 1); + mx->processed = TRUE; + } + else { + /* Display server message in chat window */ + serv_got_chat_in(mx->session->con, multimx->chatid, "MXit", PURPLE_MESSAGE_SYSTEM, msg, mx->timestamp); + mx->processed = TRUE; + } + } +} + + + +/* ------------------------------------------------------------------------------------------------- + * Callbacks from libpurple + * ------------------------------------------------------------------------------------------------- */ + +/*------------------------------------------------------------------------ + * User has selected "Add Chat" from the main menu. + * + * @param gc The connection object + * @return A list of chat configuration values + */ +GList* mxit_chat_info(PurpleConnection *gc) +{ + GList *m = NULL; + struct proto_chat_entry *pce; + + /* Configuration option: Room Name */ + pce = g_new0(struct proto_chat_entry, 1); + pce->label = "_Room Name:"; + pce->identifier = "room"; + pce->required = TRUE; + m = g_list_append(m, pce); + + return m; +} + + +/*------------------------------------------------------------------------ + * User has joined a chatroom, either because they are creating it or they + * accepted an invite. + * + * @param gc The connection object + * @param components The list of chat configuration values + */ +void mxit_chat_join(PurpleConnection *gc, GHashTable *components) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + const char* roomname = NULL; + struct multimx* multimx = NULL; + + purple_debug_info(MXIT_PLUGIN_ID, "mxit_chat_join\n"); + + /* Determine if groupchat already exists */ + roomname = g_hash_table_lookup(components, "room"); + multimx = find_room_by_alias(session, roomname); + + if (multimx != NULL) { + /* The room information already exists */ + + if (multimx->state == STATE_INVITED) { + /* Invite is pending */ + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i accept sent\n", multimx->chatid); + + /* Send Subscription Accept to MXit */ + mxit_send_allow_sub(session, multimx->roomid, multimx->roomname); + } + else { + /* Join existing room */ + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i rejoined\n", multimx->chatid); + + serv_got_joined_chat(gc, multimx->chatid, multimx->roomname); + } + } + else { + /* Send Groupchat Create to MXit */ + mxit_send_groupchat_create(session, roomname, 0, NULL); + } +} + + +/*------------------------------------------------------------------------ + * User has rejected an invite to join a MultiMX room. + * + * @param gc The connection object + * @param components The list of chat configuration values + */ +void mxit_chat_reject(PurpleConnection *gc, GHashTable* components) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + const char* roomname = NULL; + struct multimx* multimx = NULL; + + purple_debug_info(MXIT_PLUGIN_ID, "mxit_chat_reject\n"); + + roomname = g_hash_table_lookup(components, "room"); + multimx = find_room_by_alias(session, roomname); + if (multimx == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Groupchat '%s' not found\n", roomname); + return; + } + + /* Send Subscription Reject to MXit */ + mxit_send_deny_sub(session, multimx->roomid); + + /* Remove from our list of rooms */ + room_remove(session, multimx); +} + + +/*------------------------------------------------------------------------ + * Return name of chatroom (on mouse hover) + * + * @param components The list of chat configuration values. + * @return The name of the chat room + */ +char* mxit_chat_name(GHashTable *components) +{ + return g_strdup(g_hash_table_lookup(components, "room")); +} + + +/*------------------------------------------------------------------------ + * User has selected to invite somebody to a chatroom. + * + * @param gc The connection object + * @param id The chat room ID + * @param msg The invitation message entered by the user + * @param name The username of the person to invite + */ +void mxit_chat_invite(PurpleConnection *gc, int id, const char *msg, const char *username) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + struct multimx* multimx = NULL; + + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat invite to '%s'\n", username); + + /* Find matching MultiMX group */ + multimx = find_room_by_id(session, id); + if (multimx == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Could not find groupchat %i\n", id); + return; + } + + /* Send invite to MXit */ + mxit_send_groupchat_invite(session, multimx->roomid, 1, &username); +} + + +/*------------------------------------------------------------------------ + * User as closed the chat window, and the chatroom is not marked as persistent. + * + * @param gc The connection object + * @param id The chat room ID + */ +void mxit_chat_leave(PurpleConnection *gc, int id) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + struct multimx* multimx = NULL; + + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i leave\n", id); + + /* Find matching multimx group */ + multimx = find_room_by_id(session, id); + if (multimx == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Could not find groupchat %i\n", id); + return; + } + + /* Send Remove Groupchat to MXit */ + mxit_send_remove(session, multimx->roomid); + + /* Remove from our list of rooms */ + room_remove(session, multimx); +} + + +/*------------------------------------------------------------------------ + * User has entered a message in a chatroom window, send it to the MXit server. + * + * @param gc The connection object + * @param id The chat room ID + * @param message The sent message data + * @param flags The message flags + * @return Indicates success / failure + */ +int mxit_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + struct multimx* multimx = NULL; + const char* nickname; + + purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i message send: '%s'\n", id, message); + + /* Find matching MultiMX group */ + multimx = find_room_by_id(session, id); + if (multimx == NULL) { + purple_debug_error(MXIT_PLUGIN_ID, "Could not find groupchat %i\n", id); + return -1; + } + + /* Send packet to MXit */ + mxit_send_message(session, multimx->roomid, message, TRUE); + + /* Determine our nickname to display */ + if (session->profile && (session->profile->nickname[0] != '\0')) /* default is profile name (since that's what everybody else sees) */ + nickname = session->profile->nickname; + else + nickname = purple_account_get_alias(purple_connection_get_account(gc)); /* local alias */ + + /* Display message in chat window */ + serv_got_chat_in(gc, id, nickname, flags, message, time(NULL)); + + return 0; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/multimx.h Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,104 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MultiMx GroupChat -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_MULTIMX_H_ +#define _MXIT_MULTIMX_H_ + +#include "roster.h" + + +/* GroupChat Room state */ +#define STATE_CREATOR 0 +#define STATE_INVITED 1 +#define STATE_JOINED 2 + +/* + * a MultiMX room + */ +struct multimx { + char roomname[MXIT_CP_MAX_ALIAS_LEN]; /* name of the room */ + char roomid[MXIT_CP_MAX_JID_LEN]; /* internal JID for room */ + int chatid; /* libpurple chat ID */ + short state; /* state */ +}; + + +/* + * Received a Subscription Request to a MultiMX room. + */ +void multimx_invite(struct MXitSession* session, struct contact* contact, const char* creator); + +/* + * MultiMX room has been added to the roster. + */ +void multimx_created(struct MXitSession* session, struct contact* contact); + +/* + * Is this username a MultiMX contact? + */ +gboolean is_multimx_contact(struct MXitSession* session, const char* username); + +/* + * Received a message from a MultiMX room. + */ +void multimx_message_received(struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags); + +/* + * User has selected "Add Chat" from the main menu. + */ +GList* mxit_chat_info(PurpleConnection *gc); + +/* + * User has joined a chatroom, either because they are creating it or they accepted an invite. + */ +void mxit_chat_join(PurpleConnection *gc, GHashTable *data); + +/* + * User has rejected an invite to join a MultiMX room. + */ +void mxit_chat_reject(PurpleConnection *gc, GHashTable* components); + +/* + * Return name of chatroom (on mouse hover) + */ +char* mxit_chat_name(GHashTable *data); + +/* + * User has selected to invite somebody to a chatroom. + */ +void mxit_chat_invite(PurpleConnection *gc, int id, const char *msg, const char *name); + +/* + * User as closed the chat window, and the chatroom is not marked as persistent. + */ +void mxit_chat_leave(PurpleConnection *gc, int id); + +/* + * User has entered a message in a chatroom window, send it to the MXit server. + */ +int mxit_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags); + + +#endif /* _MXIT_MULTIMX_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/mxit.c Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,694 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit libPurple plugin API -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <glib.h> +#include <stdio.h> +#include <string.h> + +#include "purple.h" +#include "notify.h" +#include "plugin.h" +#include "version.h" + +#include "mxit.h" +#include "protocol.h" +#include "login.h" +#include "roster.h" +#include "chunk.h" +#include "filexfer.h" +#include "actions.h" +#include "multimx.h" + + +#ifdef MXIT_LINK_CLICK + + +/* pidgin callback function pointers for URI click interception */ +static void *(*mxit_pidgin_uri_cb)(const char *uri); +static PurpleNotifyUiOps* mxit_nots_override_original; +static PurpleNotifyUiOps mxit_nots_override; +static int not_link_ref_count = 0; + + +/*------------------------------------------------------------------------ + * Handle an URI clicked on the UI + * + * @param link the link name which has been clicked + */ +static void* mxit_link_click( const char* link64 ) +{ + PurpleAccount* account; + PurpleConnection* con; + gchar** parts = NULL; + gchar* link = NULL; + unsigned int len; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_link_click (%s)\n", link64 ); + + if ( g_ascii_strncasecmp( link64, MXIT_LINK_PREFIX, strlen( MXIT_LINK_PREFIX ) ) != 0 ) { + /* this is not for us */ + goto skip; + } + + /* decode the base64 payload */ + link = (gchar*) purple_base64_decode( link64 + strlen( MXIT_LINK_PREFIX ), &len ); + purple_debug_info( MXIT_PLUGIN_ID, "Clicked Link: '%s'\n", link ); + + parts = g_strsplit( link, "|", 5 ); + + /* check if this is a valid mxit link */ + if ( ( !parts ) || ( !parts[0] ) || ( !parts[1] ) || ( !parts[2] ) || ( !parts[3] ) || ( !parts[4] ) ) { + /* this is not for us */ + goto skip; + } + else if ( g_ascii_strcasecmp( parts[0], MXIT_LINK_KEY ) != 0 ) { + /* this is not for us */ + goto skip; + } + + /* find the account */ + account = purple_accounts_find( parts[1], parts[2] ); + if ( !account ) + goto skip; + con = purple_account_get_connection( account ); + + /* send click message back to MXit */ + mxit_send_message( con->proto_data, parts[3], parts[4], FALSE ); + + g_free( link ); + link = NULL; + g_strfreev( parts ); + parts = NULL; + + return (void*) link64; + +skip: + /* this is not an internal mxit link */ + + if ( link ) + g_free( link ); + link = NULL; + + if ( parts ) + g_strfreev( parts ); + parts = NULL; + + if ( mxit_pidgin_uri_cb ) + return mxit_pidgin_uri_cb( link64 ); + else + return (void*) link64; +} + + +/*------------------------------------------------------------------------ + * Register MXit to receive URI click notifications from the UI + */ +void mxit_register_uri_handler() +{ + not_link_ref_count++; + if ( not_link_ref_count == 1 ) { + /* make copy of notifications */ + mxit_nots_override_original = purple_notify_get_ui_ops(); + memcpy( &mxit_nots_override, mxit_nots_override_original, sizeof( PurpleNotifyUiOps ) ); + + /* save previously configured callback function pointer */ + mxit_pidgin_uri_cb = mxit_nots_override.notify_uri; + + /* override the URI function call with MXit's own one */ + mxit_nots_override.notify_uri = mxit_link_click; + purple_notify_set_ui_ops( &mxit_nots_override ); + } +} + + +/*------------------------------------------------------------------------ + * Unegister MXit from receiving URI click notifications from the UI + */ +static void mxit_unregister_uri_handler() +{ + not_link_ref_count--; + if ( not_link_ref_count == 0 ) { + /* restore the notifications to its original state */ + purple_notify_set_ui_ops( mxit_nots_override_original ); + } +} + +#endif + + +/*------------------------------------------------------------------------ + * This gets called when a new chat conversation is opened by the user + * + * @param conv The conversation object + * @param session The MXit session object + */ +static void mxit_cb_chat_created( PurpleConversation* conv, struct MXitSession* session ) +{ + PurpleConnection* gc; + struct contact* contact; + PurpleBuddy* buddy; + const char* who; + + gc = purple_conversation_get_gc( conv ); + if ( session->con != gc ) { + /* not our conversation */ + return; + } + else if ( purple_conversation_get_type( conv ) != PURPLE_CONV_TYPE_IM ) { + /* wrong type of conversation */ + return; + } + + /* get the contact name */ + who = purple_conversation_get_name( conv ); + if ( !who ) + return; + + purple_debug_info( MXIT_PLUGIN_ID, "Conversation started with '%s'\n", who ); + + /* find the buddy object */ + buddy = purple_find_buddy( session->acc, who ); + if ( ( !buddy ) || ( !buddy->proto_data ) ) + return; + + /* we ignore all conversations with which we have chatted with in this session */ + if ( find_active_chat( session->active_chats, who ) ) + return; + + /* determite if this buddy is a MXit service */ + contact = buddy->proto_data; + switch ( contact->type ) { + case MXIT_TYPE_BOT : + case MXIT_TYPE_CHATROOM : + case MXIT_TYPE_GALLERY : + case MXIT_TYPE_INFO : + serv_got_im( session->con, who, "<font color=\"#999999\">Loading menu...</font>\n", PURPLE_MESSAGE_NOTIFY, time( NULL ) ); + mxit_send_message( session, who, " ", FALSE ); + default : + break; + } +} + + +/*------------------------------------------------------------------------ + * Enable some signals to handled by our plugin + * + * @param session The MXit session object + */ +void mxit_enable_signals( struct MXitSession* session ) +{ + /* enable the signal when a new conversation is opened by the user */ + purple_signal_connect_priority( purple_conversations_get_handle(), "conversation-created", session, PURPLE_CALLBACK( mxit_cb_chat_created ), + session, PURPLE_SIGNAL_PRIORITY_HIGHEST ); +} + + +/*------------------------------------------------------------------------ + * Disable some signals handled by our plugin + * + * @param session The MXit session object + */ +static void mxit_disable_signals( struct MXitSession* session ) +{ + /* disable the signal when a new conversation is opened by the user */ + purple_signal_disconnect( purple_conversations_get_handle(), "conversation-created", session, PURPLE_CALLBACK( mxit_cb_chat_created ) ); +} + + +/*------------------------------------------------------------------------ + * Return the base icon name. + * + * @param account The MXit account object + * @param buddy The buddy + * @return The icon name (excluding extension) + */ +static const char* mxit_list_icon( PurpleAccount* account, PurpleBuddy* buddy ) +{ + return "mxit"; +} + + +/*------------------------------------------------------------------------ + * Return the emblem icon name. + * + * @param buddy The buddy + * @return The icon name (excluding extension) + */ +static const char* mxit_list_emblem( PurpleBuddy* buddy ) +{ + struct contact* contact = buddy->proto_data; + + if ( !contact ) + return NULL; + + switch ( contact-> type ) { + case MXIT_TYPE_JABBER : /* external contacts via MXit */ + case MXIT_TYPE_MSN : + case MXIT_TYPE_YAHOO : + case MXIT_TYPE_ICQ : + case MXIT_TYPE_AIM : + case MXIT_TYPE_QQ : + case MXIT_TYPE_WV : + return "external"; + + case MXIT_TYPE_BOT : /* MXit services */ + case MXIT_TYPE_GALLERY : + case MXIT_TYPE_INFO : + return "bot"; + + case MXIT_TYPE_CHATROOM : /* MXit group chat services */ + case MXIT_TYPE_MULTIMX : + default: + return NULL; + } +} + + +/*------------------------------------------------------------------------ + * Return short string representing buddy's status for display on buddy list. + * Returns status message (if one is set), or otherwise the mood. + * + * @param buddy The buddy. + * @return The status text + */ +char* mxit_status_text( PurpleBuddy* buddy ) +{ + struct contact* contact = buddy->proto_data; + + if ( !contact ) + return NULL; + + if ( contact->statusMsg ) { + /* status message */ + return g_strdup( contact-> statusMsg ); + } + else { + /* mood */ + return g_strdup( mxit_convert_mood_to_name( contact->mood ) ); + } +} + + +/*------------------------------------------------------------------------ + * Return UI tooltip information for a buddy when hovering in buddy list. + * + * @param buddy The buddy + * @param info The tooltip info being returned + * @param full Return full or summarized information + */ +static void mxit_tooltip( PurpleBuddy* buddy, PurpleNotifyUserInfo* info, gboolean full ) +{ + struct contact* contact = buddy->proto_data; + + if ( !contact ) + return; + + /* status (reference: "libpurple/notify.h") */ + if ( contact->presence != MXIT_PRESENCE_OFFLINE ) + purple_notify_user_info_add_pair( info, _( "Status" ), mxit_convert_presence_to_name( contact->presence ) ); + + /* status message */ + if ( contact->statusMsg ) + purple_notify_user_info_add_pair( info, _( "Status Message" ), contact->statusMsg ); + + /* mood */ + if ( contact->mood != MXIT_MOOD_NONE ) + purple_notify_user_info_add_pair( info, _( "Mood" ), mxit_convert_mood_to_name( contact->mood ) ); + + /* subscription type */ + if ( contact->subtype != 0 ) + purple_notify_user_info_add_pair( info, _( "Subscription" ), mxit_convert_subtype_to_name( contact->subtype ) ); + + /* hidden number */ + if ( contact->flags & MXIT_CFLAG_HIDDEN ) + purple_notify_user_info_add_pair( info, _( "Hidden Number" ), "Yes" ); +} + + +/*------------------------------------------------------------------------ + * Initiate the logout sequence, close the connection and clear the session data. + * + * @param gc The connection object + */ +static void mxit_close( PurpleConnection* gc ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + + /* disable signals */ + mxit_disable_signals( session ); + + /* close the connection */ + mxit_close_connection( session ); + +#ifdef MXIT_LINK_CLICK + /* unregister for uri click notification */ + mxit_unregister_uri_handler(); +#endif + + purple_debug_info( MXIT_PLUGIN_ID, "Releasing the session object..\n" ); + + /* free the session memory */ + g_free( session ); + session = NULL; +} + + +/*------------------------------------------------------------------------ + * Send a message to a contact + * + * @param gc The connection object + * @param who The username of the recipient + * @param message The message text + * @param flags Message flags (defined in conversation.h) + * @return Positive value (success, and echo to conversation window) + Zero (success, no echo) + Negative value (error) + */ +static int mxit_send_im( PurpleConnection* gc, const char* who, const char* message, PurpleMessageFlags flags ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "Sending message '%s' to buddy '%s'\n", message, who ); + + mxit_send_message( gc->proto_data, who, message, TRUE ); + + return 1; /* echo to conversation window */ +} + + +/*------------------------------------------------------------------------ + * The user changed their current presence state. + * + * @param account The MXit account object + * @param status The new status (libPurple status type) + */ +static void mxit_set_status( PurpleAccount* account, PurpleStatus* status ) +{ + struct MXitSession* session = purple_account_get_connection( account )->proto_data; + const char* statusid; + int presence; + char* statusmsg1; + char* statusmsg2; + + /* get the status id (reference: "libpurple/status.h") */ + statusid = purple_status_get_id( status ); + + /* convert the purple status to a mxit status */ + presence = mxit_convert_presence( statusid ); + if ( presence < 0 ) { + /* error, status not found */ + purple_debug_info( MXIT_PLUGIN_ID, "Presence status NOT found! (id = %s)\n", statusid ); + return; + } + + statusmsg1 = purple_markup_strip_html( purple_status_get_attr_string( status, "message" ) ); + statusmsg2 = g_strndup( statusmsg1, CP_MAX_STATUS_MSG ); + purple_debug_info( MXIT_PLUGIN_ID, "mxit_set_status: '%s'\n", statusmsg2 ); + + /* update presence state */ + mxit_send_presence( session, presence, statusmsg2 ); + + g_free( statusmsg1 ); + g_free( statusmsg2 ); +} + + +/*------------------------------------------------------------------------ + * MXit supports messages to offline contacts. + * + * @param buddy The buddy + */ +static gboolean mxit_offline_message( const PurpleBuddy *buddy ) +{ + return TRUE; +} + + +/*------------------------------------------------------------------------ + * Free the resources used to store a buddy. + * + * @param buddy The buddy + */ +static void mxit_free_buddy( PurpleBuddy* buddy ) +{ + struct contact* contact; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_free_buddy\n" ); + + contact = buddy->proto_data; + if ( contact ) { + if ( contact->statusMsg ) + g_free( contact->statusMsg ); + if ( contact->avatarId ) + g_free( contact->avatarId ); + g_free( contact ); + } + buddy->proto_data = NULL; +} + + +/*------------------------------------------------------------------------ + * Periodic task called every KEEPALIVE_INTERVAL (30 sec) to to maintain + * idle connections, timeouts and the transmission queue to the MXit server. + * + * @param gc The connection object + */ +static void mxit_keepalive( PurpleConnection *gc ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + + /* if not logged in, there is nothing to do */ + if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) + return; + + /* pinging is only for socket connections (HTTP does polling) */ + if ( session->http ) + return; + + if ( session->last_tx <= time( NULL ) - MXIT_PING_INTERVAL ) { + /* + * this connection has been idle for too long, better ping + * the server before it kills our connection. + */ + mxit_send_ping( session ); + } +} + + +/*------------------------------------------------------------------------ + * Set or clear our Buddy icon. + * + * @param gc The connection object + * @param img The buddy icon data + */ +static void mxit_set_buddy_icon( PurpleConnection *gc, PurpleStoredImage *img ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + + if ( img == NULL ) + mxit_set_avatar( session, NULL, 0 ); + else + mxit_set_avatar( session, purple_imgstore_get_data( img ), purple_imgstore_get_size( img ) ); +} + + +/*------------------------------------------------------------------------ + * Request profile information for another MXit contact. + * + * @param gc The connection object + * @param who The username of the contact. + */ +static void mxit_get_info( PurpleConnection *gc, const char *who ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + const char* profilelist[] = { CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_HIDENUMBER, CP_PROFILE_FULLNAME, + CP_PROFILE_TITLE, CP_PROFILE_FIRSTNAME, CP_PROFILE_LASTNAME, CP_PROFILE_EMAIL }; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_get_info: '%s'\n", who ); + + + /* send profile request */ + mxit_send_extprofile_request( session, who, ARRAY_SIZE( profilelist ), profilelist ); +} + + +/*------------------------------------------------------------------------ + * Return a list of labels to be used by Pidgin for assisting the user. + */ +static GHashTable* mxit_get_text_table( PurpleAccount* acc ) +{ + GHashTable* table; + + table = g_hash_table_new( g_str_hash, g_str_equal ); + + g_hash_table_insert( table, "login_label", _( "Your Mobile Number..." ) ); + + return table; +} + +/*========================================================================================================================*/ + +static PurplePluginProtocolInfo proto_info = { + OPT_PROTO_REGISTER_NOSCREENNAME | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_IM_IMAGE, /* options */ + NULL, /* user_splits */ + NULL, /* protocol_options */ + { /* icon_spec */ + "png", /* format */ + 32, 32, /* min width & height */ + MXIT_AVATAR_SIZE, /* max width */ + MXIT_AVATAR_SIZE, /* max height */ + 100000, /* max filezize */ + PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY /* scaling rules */ + }, + mxit_list_icon, /* list_icon */ + mxit_list_emblem, /* list_emblem */ + mxit_status_text, /* status_text */ + mxit_tooltip, /* tooltip_text */ + mxit_status_types, /* status types [roster.c] */ + NULL, /* blist_node_menu */ + mxit_chat_info, /* chat_info [multimx.c] */ + NULL, /* chat_info_defaults */ + mxit_login, /* login [login.c] */ + mxit_close, /* close */ + mxit_send_im, /* send_im */ + NULL, /* set_info */ + NULL, /* send_typing */ + mxit_get_info, /* get_info */ + mxit_set_status, /* set_status */ + NULL, /* set_idle */ + NULL, /* change_passwd */ + mxit_add_buddy, /* add_buddy [roster.c] */ + NULL, /* add_buddies */ + mxit_remove_buddy, /* remove_buddy [roster.c] */ + NULL, /* remove_buddies */ + NULL, /* add_permit */ + NULL, /* add_deny */ + NULL, /* rem_permit */ + NULL, /* rem_deny */ + NULL, /* set_permit_deny */ + mxit_chat_join, /* join_chat [multimx.c] */ + mxit_chat_reject, /* reject chat invite [multimx.c] */ + mxit_chat_name, /* get_chat_name [multimx.c] */ + mxit_chat_invite, /* chat_invite [multimx.c] */ + mxit_chat_leave, /* chat_leave [multimx.c] */ + NULL, /* chat_whisper */ + mxit_chat_send, /* chat_send [multimx.c] */ + mxit_keepalive, /* keepalive */ + mxit_register, /* register_user */ + NULL, /* get_cb_info */ + NULL, /* get_cb_away */ + mxit_buddy_alias, /* alias_buddy [roster.c] */ + mxit_buddy_group, /* group_buddy [roster.c] */ + mxit_rename_group, /* rename_group [roster.c] */ + mxit_free_buddy, /* buddy_free */ + NULL, /* convo_closed */ + NULL, /* normalize */ + mxit_set_buddy_icon, /* set_buddy_icon */ + NULL, /* remove_group */ // TODO: Add function to move all contacts out of this group (cmd=30 - remove group)? + NULL, /* get_cb_real_name */ + NULL, /* set_chat_topic */ + NULL, /* find_blist_chat */ + NULL, /* roomlist_get_list */ + NULL, /* roomlist_cancel */ + NULL, /* roomlist_expand_category */ + mxit_xfer_enabled, /* can_receive_file [filexfer.c] */ + mxit_xfer_tx, /* send_file [filexfer.c */ + mxit_xfer_new, /* new_xfer [filexfer.c] */ + mxit_offline_message, /* offline_message */ + NULL, /* whiteboard_prpl_ops */ + NULL, /* send_raw */ + NULL, /* roomlist_room_serialize */ + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* attention_types */ + sizeof( PurplePluginProtocolInfo ), /* struct_size */ + mxit_get_text_table, /* get_account_text_table */ + NULL, + NULL +}; + + +static PurplePluginInfo plugin_info = { + PURPLE_PLUGIN_MAGIC, /* purple magic, this must always be PURPLE_PLUGIN_MAGIC */ + PURPLE_MAJOR_VERSION, /* libpurple version */ + PURPLE_MINOR_VERSION, /* libpurple version */ + PURPLE_PLUGIN_PROTOCOL, /* plugin type (connecting to another network) */ + NULL, /* UI requirement (NULL for core plugin) */ + 0, /* plugin flags (zero is default) */ + NULL, /* plugin dependencies (set this value to NULL no matter what) */ + PURPLE_PRIORITY_DEFAULT, /* libpurple priority */ + + MXIT_PLUGIN_ID, /* plugin id (must be unique) */ + MXIT_PLUGIN_NAME, /* plugin name (this will be displayed in the UI) */ + MXIT_PLUGIN_VERSION, /* version of the plugin */ + + MXIT_PLUGIN_SUMMARY, /* short summary of the plugin */ + MXIT_PLUGIN_DESC, /* description of the plugin (can be long) */ + MXIT_PLUGIN_EMAIL, /* plugin author name and email address */ + MXIT_PLUGIN_WWW, /* plugin website (to find new versions and reporting of bugs) */ + + NULL, /* function pointer for loading the plugin */ + NULL, /* function pointer for unloading the plugin */ + NULL, /* function pointer for destroying the plugin */ + + NULL, /* pointer to an UI-specific struct */ + &proto_info, /* pointer to either a PurplePluginLoaderInfo or PurplePluginProtocolInfo struct */ + NULL, /* pointer to a PurplePluginUiInfo struct */ + mxit_actions, /* function pointer where you can define plugin-actions */ + + /* padding */ + NULL, /* pointer reserved for future use */ + NULL, /* pointer reserved for future use */ + NULL, /* pointer reserved for future use */ + NULL /* pointer reserved for future use */ +}; + + +/*------------------------------------------------------------------------ + * Initialising the MXit plugin. + * + * @param plugin The plugin object + */ +static void init_plugin( PurplePlugin* plugin ) +{ + PurpleAccountOption* option; + + purple_debug_info( MXIT_PLUGIN_ID, "Loading MXit libPurple plugin...\n" ); + + /* Configuration options */ + + /* WAP server (reference: "libpurple/accountopt.h") */ + option = purple_account_option_string_new( _( "WAP Server" ), MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE ); + proto_info.protocol_options = g_list_append( proto_info.protocol_options, option ); + + option = purple_account_option_bool_new( _( "Connect via HTTP" ), MXIT_CONFIG_USE_HTTP, FALSE ); + proto_info.protocol_options = g_list_append( proto_info.protocol_options, option ); + + option = purple_account_option_bool_new( _( "Enable splash-screen popup" ), MXIT_CONFIG_SPLASHPOPUP, FALSE ); + proto_info.protocol_options = g_list_append( proto_info.protocol_options, option ); + + g_assert( sizeof( struct raw_chunk ) == 5 ); +} + +PURPLE_INIT_PLUGIN( mxit, init_plugin, plugin_info ); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/mxit.h Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,197 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit libPurple plugin API -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_H_ +#define _MXIT_H_ + + +/* internationalize feedback strings */ +#ifndef _ +#ifdef GETTEXT_PACKAGE +#include <glib/gi18n-lib.h> +#else +#define _( x ) ( x ) +#endif +#endif + + +#if defined( __APPLE__ ) +/* apple architecture */ +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX 512 +#endif +#elif defined( _WIN32 ) +/* windows architecture */ +#define HOST_NAME_MAX 512 +#include "libc_interface.h" +#elif defined( __linux__ ) +/* linux architecture */ +#include <net/if.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#else +/* other architecture */ +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX 512 +#endif +#endif + + +#include "protocol.h" +#include "profile.h" + + +/* Plugin details */ +#define MXIT_PLUGIN_ID "prpl-loubserp-mxit" +#define MXIT_PLUGIN_NAME "MXit" +#define MXIT_PLUGIN_VERSION "2.2.0" +#define MXIT_PLUGIN_EMAIL "Pieter Loubser <libpurple@mxit.com>" +#define MXIT_PLUGIN_WWW "http://www.mxit.com" +#define MXIT_PLUGIN_SUMMARY "MXit Protocol Plugin" +#define MXIT_PLUGIN_DESC "MXit" + +#define MXIT_HTTP_USERAGENT "libpurple-"MXIT_PLUGIN_VERSION + + +/* default connection settings */ +#define DEFAULT_SERVER "stream.mxit.co.za" +#define DEFAULT_PORT 9119 +#define DEFAULT_WAPSITE "http://www.mxit.com" +#define DEFAULT_HTTP_SERVER "http://int.poll.mxit.com:80/mxit" + + +/* Purple account configuration variable names */ +#define MXIT_CONFIG_STATE "state" +#define MXIT_CONFIG_WAPSERVER "wap_server" +#define MXIT_CONFIG_DISTCODE "distcode" +#define MXIT_CONFIG_CLIENTKEY "clientkey" +#define MXIT_CONFIG_DIALCODE "dialcode" +#define MXIT_CONFIG_SERVER_ADDR "server" +#define MXIT_CONFIG_SERVER_PORT "port" +#define MXIT_CONFIG_HTTPSERVER "httpserver" +#define MXIT_CONFIG_SPLASHID "splashid" +#define MXIT_CONFIG_SPLASHCLICK "splashclick" +#define MXIT_CONFIG_SPLASHPOPUP "splashpopup" +#define MXIT_CONFIG_COUNTRYCODE "cc" +#define MXIT_CONFIG_LOCALE "locale" +#define MXIT_CONFIG_USE_HTTP "use_http" + + +/* account states */ +#define MXIT_STATE_LOGIN 0x00 +#define MXIT_STATE_REGISTER1 0x01 +#define MXIT_STATE_REGISTER2 0x02 + + +/* Client session flags */ +#define MXIT_FLAG_CONNECTED 0x01 /* established connection to the server */ +#define MXIT_FLAG_LOGGEDIN 0x02 /* user currently logged in */ +#define MXIT_FLAG_FIRSTROSTER 0x04 /* set to true once the first roster update has been recevied and processed */ + + +/* define this to enable the link clicking support */ +#define MXIT_LINK_CLICK + + +#ifdef MXIT_LINK_CLICK +#define MXIT_LINK_PREFIX "gopher://" +#define MXIT_LINK_KEY "MXIT" +#endif + + +#define ARRAY_SIZE( x ) ( sizeof( x ) / sizeof( x[0] ) ) + + +/* + * data structure containing all MXit session information + */ +struct MXitSession { + /* socket connection */ + char server[HOST_NAME_MAX]; /* MXit server name to connect to */ + int port; /* MXit server port to connect on */ + int fd; /* connection file descriptor */ + + /* http connection */ + gboolean http; /* connect to MXit via HTTP and not by socket */ + char http_server[HOST_NAME_MAX]; /* MXit HTTP server */ + unsigned int http_sesid; /* HTTP session id */ + unsigned int http_seqno; /* HTTP request sequence number */ + guint http_timer_id; /* timer resource id (pidgin) */ + int http_interval; /* poll inverval */ + time_t http_last_poll; /* the last time a poll has been sent */ + guint http_handler; /* HTTP connection handler */ + void* http_out_req; /* HTTP outstanding request */ + + /* client */ + struct login_data* logindata; + char* encpwd; /* encrypted password */ + char distcode[64]; /* distribution code */ + char clientkey[16]; /* client key */ + char dialcode[8]; /* dialing code */ + short flags; /* client session flags (see above) */ + + /* personal (profile) */ + struct MXitProfile* profile; /* user's profile information */ + int mood; /* user's current mood */ + + /* libpurple */ + PurpleAccount* acc; /* pointer to the libpurple internal account struct */ + PurpleConnection* con; /* pointer to the libpurple internal connection struct */ + + /* transmit */ + struct tx_queue queue; /* transmit packet queue (FIFO mode) */ + time_t last_tx; /* timestamp of last packet sent */ + int outack; /* outstanding ack packet */ + guint q_timer; /* timer handler for managing queue */ + + /* receive */ + char rx_lbuf[16]; /* receive byte buffer (socket packet length) */ + char rx_dbuf[CP_MAX_PACKET]; /* receive byte buffer (raw data) */ + unsigned int rx_i; /* receive buffer current index */ + int rx_res; /* amount of bytes still outstanding for the current packet */ + char rx_state; /* current receiver state */ + time_t last_rx; /* timestamp of last packet received */ + GList* active_chats; /* list of all our contacts we received messages from (active chats) */ + + /* groupchat */ + GList* rooms; /* active groupchat rooms */ + + /* inline images */ + GHashTable* iimages; /* table which maps inline images (including emoticons) to purple's imgstore id's */ +}; + + +char* mxit_status_text( PurpleBuddy* buddy ); +void mxit_enable_signals( struct MXitSession* session ); + +#ifdef MXIT_LINK_CLICK +void mxit_register_uri_handler(); +#endif + + +#endif /* _MXIT_H_ */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/profile.c Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,160 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user profile's -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <ctype.h> +#include <string.h> + +#include "purple.h" + +#include "mxit.h" +#include "profile.h" +#include "roster.h" + + +/*------------------------------------------------------------------------ + * Returns true if it is a valid date. + * + * @param bday Date-of-Birth string + * @return TRUE if valid, else FALSE + */ +gboolean validateDate( const char* bday ) +{ + struct tm* tm; + time_t t; + int cur_year; + int max_days[13] = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + char date[16]; + int year; + int month; + int day; + + /* validate length */ + if ( strlen( bday ) != 10 ) { + return FALSE; + } + + /* validate the format */ + if ( ( !isdigit( bday[0] ) ) || ( !isdigit( bday[1] ) ) || ( !isdigit( bday[2] ) ) || ( !isdigit( bday[3] ) ) || /* year */ + ( bday[4] != '-' ) || + ( !isdigit( bday[5] ) ) || ( !isdigit( bday[6] ) ) || /* month */ + ( bday[7] != '-' ) || + ( !isdigit( bday[8] ) ) || ( !isdigit( bday[9] ) ) ) { /* day */ + return FALSE; + } + + /* convert */ + t = time( NULL ); + tm = gmtime( &t ); + cur_year = tm->tm_year + 1900; + memcpy( date, bday, 10 ); + date[4] = '\0'; + date[7] = '\0'; + date[10] = '\0'; + year = atoi( &date[0] ); + month = atoi( &date[5] ); + day = atoi( &date[8] ); + + /* validate month */ + if ( ( month < 1 ) || ( month > 12 ) ) { + return FALSE; + } + + /* validate day */ + if ( ( day < 1 ) || ( day > max_days[month] ) ) { + return FALSE; + } + + /* validate year */ + if ( ( year < ( cur_year - 100 ) ) || ( year >= cur_year ) ) { + /* you are either tooo old or tooo young to join mxit... sorry */ + return FALSE; + } + + /* special case leap-year */ + if ( ( year % 4 != 0 ) && ( month == 2 ) && ( day == 29 ) ) { + /* cannot have 29 days in February in non leap-years! */ + return FALSE; + } + + return TRUE; +} + + +/*------------------------------------------------------------------------ + * Display the profile information. + * + * @param session The MXit session object + * @param username The username who's profile information this is + * @param profile The profile + */ +void mxit_show_profile( struct MXitSession* session, const char* username, struct MXitProfile* profile ) +{ + PurpleNotifyUserInfo* info = purple_notify_user_info_new(); + struct contact* contact = NULL; + PurpleBuddy* buddy; + + buddy = purple_find_buddy( session->acc, username ); + if ( buddy ) { + purple_notify_user_info_add_pair( info, _( "Alias" ), buddy->alias ); + purple_notify_user_info_add_section_break( info ); + contact = buddy->proto_data; + } + + purple_notify_user_info_add_pair( info, _( "Nick Name" ), profile->nickname ); + purple_notify_user_info_add_pair( info, _( "Birthday" ), profile->birthday ); + purple_notify_user_info_add_pair( info, _( "Gender" ), profile->male ? _( "Male" ) : _( "Female" ) ); + purple_notify_user_info_add_pair( info, _( "Hidden Number" ), profile->hidden ? _( "Yes" ) : _( "No" ) ); + + purple_notify_user_info_add_section_break( info ); + + /* optional information */ + purple_notify_user_info_add_pair( info, _( "Title" ), profile->title ); + purple_notify_user_info_add_pair( info, _( "First Name" ), profile->firstname ); + purple_notify_user_info_add_pair( info, _( "Last Name" ), profile->lastname ); + purple_notify_user_info_add_pair( info, _( "Email" ), profile->email ); + + purple_notify_user_info_add_section_break( info ); + + if ( contact ) { + /* presence */ + purple_notify_user_info_add_pair( info, _( "Status" ), mxit_convert_presence_to_name( contact->presence ) ); + + /* mood */ + if ( contact->mood != MXIT_MOOD_NONE ) + purple_notify_user_info_add_pair( info, _( "Mood" ), mxit_convert_mood_to_name( contact->mood ) ); + else + purple_notify_user_info_add_pair( info, _( "Mood" ), _( "None" ) ); + + /* status message */ + if ( contact->statusMsg ) + purple_notify_user_info_add_pair( info, _( "Status Message" ), contact->statusMsg ); + + /* subscription type */ + purple_notify_user_info_add_pair( info, _( "Subscription" ), mxit_convert_subtype_to_name( contact->subtype ) ); + } + + purple_notify_userinfo( session->con, username, info, NULL, NULL ); + purple_notify_user_info_destroy( info ); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/profile.h Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,56 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user profile's -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_PROFILE_H_ +#define _MXIT_PROFILE_H_ + +#include <glib.h> + + +struct MXitProfile { + /* required */ + char loginname[64]; /* name user uses to log into MXit with (aka 'mxitid') */ + char nickname[64]; /* user's own display name (aka 'nickname', aka 'fullname', aka 'alias') in MXit */ + char birthday[16]; /* user's birthday "YYYY-MM-DD" */ + gboolean male; /* true if the user's gender is male (otherwise female) */ + char pin[16]; /* user's password */ + + /* optional */ + char title[32]; /* user's title */ + char firstname[64]; /* user's first name */ + char lastname[64]; /* user's last name (aka 'surname') */ + char email[64]; /* user's email address */ + char mobilenr[21]; /* user's mobile number */ + + gboolean hidden; /* set if the user's msisdn should remain hidden */ +}; + +struct MXitSession; +void mxit_show_profile( struct MXitSession* session, const char* username, struct MXitProfile* profile ); + +gboolean validateDate( const char* bday ); + + +#endif /* _MXIT_PROFILE_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/protocol.c Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,2442 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit client protocol implementation -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "purple.h" + +#include "protocol.h" +#include "mxit.h" +#include "roster.h" +#include "chunk.h" +#include "filexfer.h" +#include "markup.h" +#include "multimx.h" +#include "splashscreen.h" +#include "login.h" +#include "formcmds.h" +#include "http.h" + + +#define MXIT_MS_OFFSET 3 + +/* configure the right record terminator char to use */ +#define CP_REC_TERM ( ( session->http ) ? CP_HTTP_REC_TERM : CP_SOCK_REC_TERM ) + + + +/*------------------------------------------------------------------------ + * Display a notification popup message to the user. + * + * @param type The type of notification: + * - info: PURPLE_NOTIFY_MSG_INFO + * - warning: PURPLE_NOTIFY_MSG_WARNING + * - error: PURPLE_NOTIFY_MSG_ERROR + * @param heading Heading text + * @param message Message text + */ +void mxit_popup( int type, const char* heading, const char* message ) +{ + /* (reference: "libpurple/notify.h") */ + purple_notify_message( NULL, type, _( MXIT_POPUP_WIN_NAME ), heading, message, NULL, NULL ); +} + + +/*------------------------------------------------------------------------ + * For compatibility with legacy clients, all usernames are sent from MXit with a domain + * appended. For MXit contacts, this domain is set to "@m". This function strips + * those fake domains. + * + * @param username The username of the contact + */ +void mxit_strip_domain( char* username ) +{ + if ( g_str_has_suffix( username, "@m" ) ) + username[ strlen(username) - 2 ] = '\0'; +} + + +/*------------------------------------------------------------------------ + * Dump a byte buffer to the console for debugging purposes. + * + * @param buf The data + * @param len The data length + */ +void dump_bytes( struct MXitSession* session, const char* buf, int len ) +{ + char msg[( len * 3 ) + 1]; + int i; + + memset( msg, 0x00, sizeof( msg ) ); + + for ( i = 0; i < len; i++ ) { + if ( buf[i] == CP_REC_TERM ) /* record terminator */ + msg[i] = '!'; + else if ( buf[i] == CP_FLD_TERM ) /* field terminator */ + msg[i] = '^'; + else if ( buf[i] == CP_PKT_TERM ) /* packet terminator */ + msg[i] = '@'; + else if ( buf[i] < 0x20 ) + msg[i] = '_'; + else + msg[i] = buf[i]; + + } + + purple_debug_info( MXIT_PLUGIN_ID, "DUMP: '%s'\n", msg ); +} + + +/*------------------------------------------------------------------------ + * Determine if we have an active chat with a specific contact + * + * @param session The MXit session object + * @param who The contact name + * @return Return true if we have an active chat with the contact + */ +gboolean find_active_chat( const GList* chats, const char* who ) +{ + const GList* list = chats; + const char* chat = NULL; + + while ( list ) { + chat = (const char*) list->data; + + if ( strcmp( chat, who ) == 0 ) + return TRUE; + + list = g_list_next( list ); + } + + return FALSE; +} + + +/*======================================================================================================================== + * Low-level Packet transmission + */ + +/*------------------------------------------------------------------------ + * Remove next packet from transmission queue. + * + * @param session The MXit session object + * @return The next packet for transmission (or NULL) + */ +static struct tx_packet* pop_tx_packet( struct MXitSession* session ) +{ + struct tx_packet* packet = NULL; + + if ( session->queue.count > 0 ) { + /* dequeue the next packet */ + packet = session->queue.packets[session->queue.rd_i]; + session->queue.packets[session->queue.rd_i] = NULL; + session->queue.rd_i = ( session->queue.rd_i + 1 ) % MAX_QUEUE_SIZE; + session->queue.count--; + } + + return packet; +} + + +/*------------------------------------------------------------------------ + * Add packet to transmission queue. + * + * @param session The MXit session object + * @param packet The packet to transmit + * @return Return TRUE if packet was enqueue, or FALSE if queue is full. + */ +static gboolean push_tx_packet( struct MXitSession* session, struct tx_packet* packet ) +{ + if ( session->queue.count < MAX_QUEUE_SIZE ) { + /* enqueue packet */ + session->queue.packets[session->queue.wr_i] = packet; + session->queue.wr_i = ( session->queue.wr_i + 1 ) % MAX_QUEUE_SIZE; + session->queue.count++; + return TRUE; + } + else + return FALSE; /* queue is full */ +} + + +/*------------------------------------------------------------------------ + * Deallocate transmission packet. + * + * @param packet The packet to deallocate. + */ +static void free_tx_packet( struct tx_packet* packet ) +{ + g_free( packet->data ); + g_free( packet ); + packet = NULL; +} + + +/*------------------------------------------------------------------------ + * Flush all the packets from the tx queue and release the resources. + * + * @param session The MXit session object + */ +static void flush_queue( struct MXitSession* session ) +{ + struct tx_packet* packet; + + purple_debug_info( MXIT_PLUGIN_ID, "flushing the tx queue\n" ); + + while ( (packet = pop_tx_packet( session ) ) != NULL ) + free_tx_packet( packet ); +} + + +/*------------------------------------------------------------------------ + * TX Step 3: Write the packet data to the TCP connection. + * + * @param fd The file descriptor + * @param pktdata The packet data + * @param pktlen The length of the packet data + * @return Return -1 on error, otherwise 0 + */ +static int mxit_write_sock_packet( int fd, const char* pktdata, int pktlen ) +{ + int written; + int res; + + written = 0; + while ( written < pktlen ) { + res = write( fd, &pktdata[written], pktlen - written ); + if ( res <= 0 ) { + /* error on socket */ + if ( errno == EAGAIN ) + continue; + + purple_debug_error( MXIT_PLUGIN_ID, "Error while writing packet to MXit server (%i)\n", res ); + return -1; + } + written += res; + } + + return 0; +} + + +/*------------------------------------------------------------------------ + * Callback called for handling a HTTP GET response + * + * @param url_data libPurple internal object (see purple_util_fetch_url_request) + * @param user_data The MXit session object + * @param url_text The data returned (could be NULL if error) + * @param len The length of the data returned (0 if error) + * @param error_message Descriptive error message + */ +static void mxit_cb_http_rx( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + + /* clear outstanding request */ + session->http_out_req = NULL; + + if ( ( !url_text ) || ( len == 0 ) ) { + /* error with request */ + purple_debug_error( MXIT_PLUGIN_ID, "HTTP response error (%s)\n", error_message ); + return; + } + + /* convert the HTTP result */ + memcpy( session->rx_dbuf, url_text, len ); + session->rx_i = len; + + mxit_parse_packet( session ); +} + + +/*------------------------------------------------------------------------ + * TX Step 3: Write the packet data to the HTTP connection (GET style). + * + * @param session The MXit session object + * @param pktdata The packet data + * @param pktlen The length of the packet data + * @return Return -1 on error, otherwise 0 + */ +static void mxit_write_http_get( struct MXitSession* session, struct tx_packet* packet ) +{ + char* part = NULL; + char* url = NULL; + + if ( packet->datalen > 0 ) { + char* tmp = NULL; + + tmp = g_strndup( packet->data, packet->datalen ); + part = g_strdup( purple_url_encode( tmp ) ); + g_free( tmp ); + } + + url = g_strdup_printf( "%s?%s%s", session->http_server, purple_url_encode( packet->header ), ( !part ) ? "" : part ); + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "HTTP GET: '%s'\n", url ); +#endif + + /* send the HTTP request */ + session->http_out_req = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_http_rx, session ); + + g_free( url ); + if ( part ) + g_free( part ); +} + + +/*------------------------------------------------------------------------ + * TX Step 3: Write the packet data to the HTTP connection (POST style). + * + * @param session The MXit session object + * @param pktdata The packet data + * @param pktlen The length of the packet data + * @return Return -1 on error, otherwise 0 + */ +static void mxit_write_http_post( struct MXitSession* session, struct tx_packet* packet ) +{ + char request[256 + packet->datalen]; + int reqlen; + char* host_name; + int host_port; + gboolean ok; + + /* extract the HTTP host name and host port number to connect to */ + ok = purple_url_parse( session->http_server, &host_name, &host_port, NULL, NULL, NULL ); + if ( !ok ) { + purple_debug_error( MXIT_PLUGIN_ID, "HTTP POST error: (host name '%s' not valid)\n", session->http_server ); + } + + /* strip off the last '&' from the header */ + packet->header[packet->headerlen - 1] = '\0'; + packet->headerlen--; + + /* build the HTTP request packet */ + reqlen = g_snprintf( request, 256, + "POST %s?%s HTTP/1.1\r\n" + "User-Agent: " MXIT_HTTP_USERAGENT "\r\n" + "Content-Type: application/octet-stream\r\n" + "Host: %s\r\n" + "Content-Length: %" G_GSIZE_FORMAT "\r\n" + "\r\n", + session->http_server, + purple_url_encode( packet->header ), + host_name, + packet->datalen - MXIT_MS_OFFSET + ); + + /* copy over the packet body data (could be binary) */ + memcpy( request + reqlen, packet->data + MXIT_MS_OFFSET, packet->datalen - MXIT_MS_OFFSET ); + reqlen += packet->datalen; + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST:\n" ); + dump_bytes( session, request, reqlen ); +#endif + + /* send the request to the HTTP server */ + mxit_http_send_request( session, host_name, host_port, request, reqlen ); +} + + +/*------------------------------------------------------------------------ + * TX Step 2: Handle the transmission of the packet to the MXit server. + * + * @param session The MXit session object + * @param packet The packet to transmit + */ +static void mxit_send_packet( struct MXitSession* session, struct tx_packet* packet ) +{ + int res; + + if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) { + /* we are not connected so ignore all packets to be send */ + purple_debug_error( MXIT_PLUGIN_ID, "Dropping TX packet (we are not connected)\n" ); + return; + } + + purple_debug_info( MXIT_PLUGIN_ID, "Packet send CMD:%i (%i)\n", packet->cmd, packet->headerlen + packet->datalen ); +#ifdef DEBUG_PROTOCOL + dump_bytes( session, packet->header, packet->headerlen ); + dump_bytes( session, packet->data, packet->datalen ); +#endif + + if ( !session->http ) { + /* socket connection */ + char data[packet->datalen + packet->headerlen]; + int datalen; + + /* create raw data buffer */ + memcpy( data, packet->header, packet->headerlen ); + memcpy( data + packet->headerlen, packet->data, packet->datalen ); + datalen = packet->headerlen + packet->datalen; + + res = mxit_write_sock_packet( session->fd, data, datalen ); + if ( res < 0 ) { + /* we must have lost the connection, so terminate it so that we can reconnect */ + purple_connection_error( session->con, _( "We have lost the connection to MXit. Please reconnect." ) ); + } + } + else { + /* http connection */ + + if ( packet->cmd == CP_CMD_MEDIA ) { + /* multimedia packets must be send with a HTTP POST */ + mxit_write_http_post( session, packet ); + } + else { + mxit_write_http_get( session, packet ); + } + } + + /* update the timestamp of the last-transmitted packet */ + session->last_tx = time( NULL ); + + /* + * we need to remember that we are still waiting for the ACK from + * the server on this request + */ + session->outack = packet->cmd; + + /* free up the packet resources */ + free_tx_packet( packet ); +} + + +/*------------------------------------------------------------------------ + * TX Step 1: Create a new Tx packet and queue it for sending. + * + * @param session The MXit session object + * @param data The packet data (payload) + * @param datalen The length of the packet data + * @param cmd The MXit command for this packet + */ +static void mxit_queue_packet( struct MXitSession* session, const char* data, int datalen, int cmd ) +{ + struct tx_packet* packet; + char header[256]; + int hlen; + + /* create a packet for sending */ + packet = g_new0( struct tx_packet, 1 ); + packet->data = g_malloc0( datalen ); + packet->cmd = cmd; + packet->headerlen = 0; + + /* create generic packet header */ + hlen = sprintf( header, "id=%s%c", session->acc->username, CP_REC_TERM ); /* client msisdn */ + + if ( session->http ) { + /* http connection only */ + hlen += sprintf( header + hlen, "s=" ); + if ( session->http_sesid > 0 ) { + hlen += sprintf( header + hlen, "%u%c", session->http_sesid, CP_FLD_TERM ); /* http session id */ + } + session->http_seqno++; + hlen += sprintf( header + hlen, "%u%c", session->http_seqno, CP_REC_TERM ); /* http request sequence id */ + } + + hlen += sprintf( header + hlen, "cm=%i%c", cmd, CP_REC_TERM ); /* packet command */ + + if ( !session->http ) { + /* socket connection only */ + packet->headerlen += sprintf( packet->header, "ln=%i%c", ( datalen + hlen ), CP_REC_TERM ); /* packet length */ + } + + /* copy the header to packet */ + memcpy( packet->header + packet->headerlen, header, hlen ); + packet->headerlen += hlen; + + /* copy payload to packet */ + if ( datalen > 0 ) + memcpy( packet->data, data, datalen ); + packet->datalen = datalen; + + + /* + * shortcut: first check if there are any commands still outstanding. + * if not, then we might as well just write this packet directly and + * skip the whole queueing thing + */ + if ( session->outack == 0 ) { + /* no outstanding ACKs, so we might as well write it directly */ + mxit_send_packet( session, packet ); + } + else { + /* ACK still outstanding, so we need to queue this request until we have the ACK */ + + if ( ( packet->cmd == CP_CMD_PING ) || ( packet->cmd == CP_CMD_POLL ) ) { + /* we do NOT queue HTTP poll nor socket ping packets */ + free_tx_packet( packet ); + return; + } + + purple_debug_info( MXIT_PLUGIN_ID, "queueing packet for later sending cmd=%i\n", cmd ); + if ( !push_tx_packet( session, packet ) ) { + /* packet could not be queued for transmission */ + mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Message Send Error" ), _( "Unable to process your request at this time" ) ); + free_tx_packet( packet ); + } + } +} + + +/*------------------------------------------------------------------------ + * Callback to manage the packet send queue (send next packet, timeout's, etc). + * + * @param session The MXit session object + */ +gboolean mxit_manage_queue( gpointer user_data ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + struct tx_packet* packet = NULL; + + if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) { + /* we are not connected, so ignore the queue */ + return TRUE; + } + else if ( session->outack > 0 ) { + /* we are still waiting for an outstanding ACK from the MXit server */ + if ( session->last_tx <= time( NULL ) - MXIT_ACK_TIMEOUT ) { + /* ack timeout! so we close the connection here */ + purple_debug_info( MXIT_PLUGIN_ID, "mxit_manage_queue: Timeout awaiting ACK for command '%X'\n", session->outack ); + purple_connection_error( session->con, _( "Timeout while waiting for a response from the MXit server." ) ); + } + return TRUE; + } + + packet = pop_tx_packet( session ); + if ( packet != NULL ) { + /* there was a packet waiting to be sent to the server, now is the time to do something about it */ + + /* send the packet to MXit server */ + mxit_send_packet( session, packet ); + } + + return TRUE; +} + + +/*------------------------------------------------------------------------ + * Callback to manage HTTP server polling (HTTP connections ONLY) + * + * @param session The MXit session object + */ +gboolean mxit_manage_polling( gpointer user_data ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + gboolean poll = FALSE; + time_t now = time( NULL ); + int polldiff; + int rxdiff; + + if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) { + /* we only poll if we are actually logged in */ + return TRUE; + } + + /* calculate the time differences */ + rxdiff = now - session->last_rx; + polldiff = now - session->http_last_poll; + + if ( rxdiff < MXIT_HTTP_POLL_MIN ) { + /* we received some reply a few moments ago, so reset the poll interval */ + session->http_interval = MXIT_HTTP_POLL_MIN; + } + else if ( session->http_last_poll < ( now - session->http_interval ) ) { + /* time to poll again */ + poll = TRUE; + + /* back-off some more with the polling */ + session->http_interval = session->http_interval + ( session->http_interval / 2 ); + if ( session->http_interval > MXIT_HTTP_POLL_MAX ) + session->http_interval = MXIT_HTTP_POLL_MAX; + } + + /* debugging */ + //purple_debug_info( MXIT_PLUGIN_ID, "POLL TIMER: %i (%i,%i)\n", session->http_interval, rxdiff, polldiff ); + + if ( poll ) { + /* send poll request */ + session->http_last_poll = time( NULL ); + mxit_send_poll( session ); + } + + return TRUE; +} + + +/*======================================================================================================================== + * Send MXit operations. + */ + +/*------------------------------------------------------------------------ + * Send a ping/keepalive packet to MXit server. + * + * @param session The MXit session object + */ +void mxit_send_ping( struct MXitSession* session ) +{ + /* queue packet for transmission */ + mxit_queue_packet( session, NULL, 0, CP_CMD_PING ); +} + + +/*------------------------------------------------------------------------ + * Send a poll request to the HTTP server (HTTP connections ONLY). + * + * @param session The MXit session object + */ +void mxit_send_poll( struct MXitSession* session ) +{ + /* queue packet for transmission */ + mxit_queue_packet( session, NULL, 0, CP_CMD_POLL ); +} + + +/*------------------------------------------------------------------------ + * Send a logout packet to the MXit server. + * + * @param session The MXit session object + */ +void mxit_send_logout( struct MXitSession* session ) +{ + /* queue packet for transmission */ + mxit_queue_packet( session, NULL, 0, CP_CMD_LOGOUT ); +} + + +/*------------------------------------------------------------------------ + * Send a register packet to the MXit server. + * + * @param session The MXit session object + */ +void mxit_send_register( struct MXitSession* session ) +{ + struct MXitProfile* profile = session->profile; + const char* locale; + char data[CP_MAX_PACKET]; + int datalen; + + locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%s%c%i%c%s%c" /* "ms"=password\1version\1maxreplyLen\1name\1 */ + "%s%c%i%c%s%c%s%c" /* dateOfBirth\1gender\1location\1capabilities\1 */ + "%s%c%i%c%s%c%s", /* dc\1features\1dialingcode\1locale */ + session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, CP_MAX_FILESIZE, CP_FLD_TERM, profile->nickname, CP_FLD_TERM, + profile->birthday, CP_FLD_TERM, ( profile->male ) ? 1 : 0, CP_FLD_TERM, MXIT_DEFAULT_LOC, CP_FLD_TERM, MXIT_CP_CAP, CP_FLD_TERM, + session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM, session->dialcode, CP_FLD_TERM, locale + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_REGISTER ); +} + + +/*------------------------------------------------------------------------ + * Send a login packet to the MXit server. + * + * @param session The MXit session object + */ +void mxit_send_login( struct MXitSession* session ) +{ + const char* splashId; + const char* locale; + char data[CP_MAX_PACKET]; + int datalen; + + locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%s%c%i%c" /* "ms"=password\1version\1getContacts\1 */ + "%s%c%s%c%i%c" /* capabilities\1dc\1features\1 */ + "%s%c%s", /* dialingcode\1locale */ + session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, 1, CP_FLD_TERM, + MXIT_CP_CAP, CP_FLD_TERM, session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM, + session->dialcode, CP_FLD_TERM, locale + ); + + /* include "custom resource" information */ + splashId = splash_current( session ); + if ( splashId != NULL ) + datalen += sprintf( data + datalen, "%ccr=%s", CP_REC_TERM, splashId ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_LOGIN ); +} + + +/*------------------------------------------------------------------------ + * Send a chat message packet to the MXit server. + * + * @param session The MXit session object + * @param to The username of the recipient + * @param msg The message text + */ +void mxit_send_message( struct MXitSession* session, const char* to, const char* msg, gboolean parse_markup ) +{ + char data[CP_MAX_PACKET]; + char* markuped_msg; + int datalen; + int msgtype = CP_MSGTYPE_NORMAL; + + /* first we need to convert the markup from libPurple to MXit format */ + if ( parse_markup ) + markuped_msg = mxit_convert_markup_tx( msg, &msgtype ); + else + markuped_msg = g_strdup( msg ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%s%c%i%c%i", /* "ms"=jid\1msg\1type\1flags */ + to, CP_FLD_TERM, markuped_msg, CP_FLD_TERM, msgtype, CP_FLD_TERM, CP_MSG_MARKUP | CP_MSG_EMOTICON + ); + + /* free the resources */ + g_free( markuped_msg ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_TX_MSG ); +} + + +/*------------------------------------------------------------------------ + * Send a extended profile request packet to the MXit server. + * + * @param session The MXit session object + * @param username Username who's profile is being requested (NULL = our own) + * @param nr_attribs Number of attributes being requested + * @param attributes The names of the attributes + */ +void mxit_send_extprofile_request( struct MXitSession* session, const char* username, unsigned int nr_attrib, const char* attribute[] ) +{ + char data[CP_MAX_PACKET]; + int datalen; + unsigned int i; + + datalen = sprintf( data, "ms=%s%c%i", /* "ms="mxitid\1nr_attributes */ + (username ? username : ""), CP_FLD_TERM, nr_attrib); + + /* add attributes */ + for ( i = 0; i < nr_attrib; i++ ) + datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, attribute[i] ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_GET ); +} + + +/*------------------------------------------------------------------------ + * Send an update profile packet to the MXit server. + * + * @param session The MXit session object + * @param password The new password to be used for logging in (optional) + * @param nr_attrib The number of attributes + * @param attributes String containing the attributes and settings seperated by '0x01' + */ +void mxit_send_extprofile_update( struct MXitSession* session, const char* password, unsigned int nr_attrib, const char* attributes ) +{ + char data[CP_MAX_PACKET]; + gchar** parts; + int datalen; + unsigned int i; + + parts = g_strsplit( attributes, "\01", ( MXIT_MAX_ATTRIBS * 3 ) ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%i", /* "ms"=password\1nr_attibutes */ + ( password ) ? password : "", CP_FLD_TERM, nr_attrib + ); + + /* add attributes */ + for ( i = 1; i < nr_attrib * 3; i+=3 ) + datalen += sprintf( data + datalen, "%c%s%c%s%c%s", /* \1name\1type\1value */ + CP_FLD_TERM, parts[i], CP_FLD_TERM, parts[i + 1], CP_FLD_TERM, parts[i + 2] ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_SET ); + + /* freeup the memory */ + g_strfreev( parts ); +} + + +/*------------------------------------------------------------------------ + * Send a presence update packet to the MXit server. + * + * @param session The MXit session object + * @param presence The presence (as per MXit types) + * @param statusmsg The status message (can be NULL) + */ +void mxit_send_presence( struct MXitSession* session, int presence, const char* statusmsg ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%i%c", /* "ms"=show\1status */ + presence, CP_FLD_TERM + ); + + /* append status message (if one is set) */ + if ( statusmsg ) + datalen += sprintf( data + datalen, "%s", statusmsg ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_STATUS ); +} + + +/*------------------------------------------------------------------------ + * Send a mood update packet to the MXit server. + * + * @param session The MXit session object + * @param mood The mood (as per MXit types) + */ +void mxit_send_mood( struct MXitSession* session, int mood ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%i", /* "ms"=mood */ + mood + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_MOOD ); +} + + +/*------------------------------------------------------------------------ + * Send an invite contact packet to the MXit server. + * + * @param session The MXit session object + * @param username The username of the contact being invited + * @param alias Our alias for the contact + * @param groupname Group in which contact should be stored. + */ +void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%s%c%s%c%i%c%s", /* "ms"=group\1username\1alias\1type\1msg */ + groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias, + CP_FLD_TERM, MXIT_TYPE_MXIT, CP_FLD_TERM, "" + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_INVITE ); +} + + +/*------------------------------------------------------------------------ + * Send a remove contact packet to the MXit server. + * + * @param session The MXit session object + * @param username The username of the contact being removed + */ +void mxit_send_remove( struct MXitSession* session, const char* username ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s", /* "ms"=username */ + username + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_REMOVE ); +} + + +/*------------------------------------------------------------------------ + * Send an accept subscription (invite) packet to the MXit server. + * + * @param session The MXit session object + * @param username The username of the contact being accepted + * @param alias Our alias for the contact + */ +void mxit_send_allow_sub( struct MXitSession* session, const char* username, const char* alias ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%s%c%s", /* "ms"=username\1group\1alias */ + username, CP_FLD_TERM, "", CP_FLD_TERM, alias + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_ALLOW ); +} + + +/*------------------------------------------------------------------------ + * Send an deny subscription (invite) packet to the MXit server. + * + * @param session The MXit session object + * @param username The username of the contact being denied + */ +void mxit_send_deny_sub( struct MXitSession* session, const char* username ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s", /* "ms"=username */ + username + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_DENY ); +} + + +/*------------------------------------------------------------------------ + * Send an update contact packet to the MXit server. + * + * @param session The MXit session object + * @param username The username of the contact being denied + * @param alias Our alias for the contact + * @param groupname Group in which contact should be stored. + */ +void mxit_send_update_contact( struct MXitSession* session, const char* username, const char* alias, const char* groupname ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%s%c%s", /* "ms"=groupname\1username\1alias */ + groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_UPDATE ); +} + + +/*------------------------------------------------------------------------ + * Send a splash-screen click event packet. + * + * @param session The MXit session object + * @param splashid The identifier of the splash-screen + */ +void mxit_send_splashclick( struct MXitSession* session, const char* splashid ) +{ + char data[CP_MAX_PACKET]; + int datalen; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s", /* "ms"=splashId */ + splashid + ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_SPLASHCLICK ); +} + + +/*------------------------------------------------------------------------ + * Send packet to create a MultiMX room. + * + * @param session The MXit session object + * @param groupname Name of the room to create + * @param nr_usernames Number of users in initial invite + * @param usernames The usernames of the users in the initial invite + */ +void mxit_send_groupchat_create( struct MXitSession* session, const char* groupname, int nr_usernames, const char* usernames[] ) +{ + char data[CP_MAX_PACKET]; + int datalen; + int i; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%i", /* "ms"=roomname\1nr_jids\1jid0\1..\1jidN */ + groupname, CP_FLD_TERM, nr_usernames + ); + + /* add usernames */ + for ( i = 0; i < nr_usernames; i++ ) + datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, usernames[i] ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_CREATE ); +} + + +/*------------------------------------------------------------------------ + * Send packet to invite users to existing MultiMX room. + * + * @param session The MXit session object + * @param roomid The unique RoomID for the MultiMx room. + * @param nr_usernames Number of users being invited + * @param usernames The usernames of the users being invited + */ + +void mxit_send_groupchat_invite( struct MXitSession* session, const char* roomid, int nr_usernames, const char* usernames[] ) +{ + char data[CP_MAX_PACKET]; + int datalen; + int i; + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=%s%c%i", /* "ms"=roomid\1nr_jids\1jid0\1..\1jidN */ + roomid, CP_FLD_TERM, nr_usernames + ); + + /* add usernames */ + for ( i = 0; i < nr_usernames; i++ ) + datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, usernames[i] ); + + /* queue packet for transmission */ + mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_INVITE ); +} + + +/*------------------------------------------------------------------------ + * Send a "send file direct" multimedia packet. + * + * @param session The MXit session object + * @param username The username of the recipient + * @param filename The name of the file being sent + * @param buf The content of the file + * @param buflen The length of the file contents + */ +void mxit_send_file( struct MXitSession* session, const char* username, const char* filename, const unsigned char* buf, int buflen ) +{ + char data[CP_MAX_PACKET]; + int datalen = 0; + struct raw_chunk* chunk; + int size; + + purple_debug_info( MXIT_PLUGIN_ID, "SENDING FILE '%s' of %i bytes to user '%s'\n", filename, buflen, username ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=" ); + + /* map chunk header over data buffer */ + chunk = (struct raw_chunk *) &data[datalen]; + + size = mxit_chunk_create_senddirect( chunk->data, username, filename, buf, buflen ); + if ( size < 0 ) { + purple_debug_error( MXIT_PLUGIN_ID, "Error creating senddirect chunk (%i)\n", size ); + return; + } + + chunk->type = CP_CHUNK_DIRECT_SND; + chunk->length = htonl( size ); + datalen += sizeof( struct raw_chunk ) + size; + + /* send the byte stream to the mxit server */ + mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); +} + + +/*------------------------------------------------------------------------ + * Send a "reject file" multimedia packet. + * + * @param session The MXit session object + * @param fileid A unique ID that identifies this file + */ +void mxit_send_file_reject( struct MXitSession* session, const char* fileid ) +{ + char data[CP_MAX_PACKET]; + int datalen = 0; + struct raw_chunk* chunk; + int size; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_reject\n" ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=" ); + + /* map chunk header over data buffer */ + chunk = (struct raw_chunk *) &data[datalen]; + + size = mxit_chunk_create_reject( chunk->data, fileid ); + if ( size < 0 ) { + purple_debug_error( MXIT_PLUGIN_ID, "Error creating reject chunk (%i)\n", size ); + return; + } + + chunk->type = CP_CHUNK_REJECT; + chunk->length = htonl( size ); + datalen += sizeof( struct raw_chunk ) + size; + + /* send the byte stream to the mxit server */ + mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); +} + + +/*------------------------------------------------------------------------ + * Send a "get file" multimedia packet. + * + * @param session The MXit session object + * @param fileid A unique ID that identifies this file + * @param filesize The number of bytes to retrieve + * @param offset Offset in file at which to start retrieving + */ +void mxit_send_file_accept( struct MXitSession* session, const char* fileid, int filesize, int offset ) +{ + char data[CP_MAX_PACKET]; + int datalen = 0; + struct raw_chunk* chunk; + int size; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_accept\n" ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=" ); + + /* map chunk header over data buffer */ + chunk = (struct raw_chunk *) &data[datalen]; + + size = mxit_chunk_create_get( chunk->data, fileid, filesize, offset ); + if ( size < 0 ) { + purple_debug_error( MXIT_PLUGIN_ID, "Error creating getfile chunk (%i)\n", size ); + return; + } + + chunk->type = CP_CHUNK_GET; + chunk->length = htonl( size ); + datalen += sizeof( struct raw_chunk ) + size; + + /* send the byte stream to the mxit server */ + mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); +} + + +/*------------------------------------------------------------------------ + * Send a "received file" multimedia packet. + * + * @param session The MXit session object + * @param status The status of the file-transfer + */ +void mxit_send_file_received( struct MXitSession* session, const char* fileid, short status ) +{ + char data[CP_MAX_PACKET]; + int datalen = 0; + struct raw_chunk* chunk; + int size; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_received\n" ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=" ); + + /* map chunk header over data buffer */ + chunk = (struct raw_chunk *) &data[datalen]; + + size = mxit_chunk_create_received( chunk->data, fileid, status ); + if ( size < 0 ) { + purple_debug_error( MXIT_PLUGIN_ID, "Error creating received chunk (%i)\n", size ); + return; + } + + chunk->type = CP_CHUNK_RECIEVED; + chunk->length = htonl( size ); + datalen += sizeof( struct raw_chunk ) + size; + + /* send the byte stream to the mxit server */ + mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); +} + + +/*------------------------------------------------------------------------ + * Send a "set avatar" multimedia packet. + * + * @param session The MXit session object + * @param data The avatar data + * @param buflen The length of the avatar data + */ +void mxit_set_avatar( struct MXitSession* session, const unsigned char* avatar, int avatarlen ) +{ + char data[CP_MAX_PACKET]; + int datalen = 0; + struct raw_chunk* chunk; + int size; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_set_avatar: %i bytes\n", avatarlen ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=" ); + + /* map chunk header over data buffer */ + chunk = (struct raw_chunk *) &data[datalen]; + + size = mxit_chunk_create_set_avatar( chunk->data, avatar, avatarlen ); + if ( size < 0 ) { + purple_debug_error( MXIT_PLUGIN_ID, "Error creating set avatar chunk (%i)\n", size ); + return; + } + + chunk->type = CP_CHUNK_SET_AVATAR; + chunk->length = htonl( size ); + datalen += sizeof( struct raw_chunk ) + size; + + /* send the byte stream to the mxit server */ + mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); +} + + +/*------------------------------------------------------------------------ + * Send a "get avatar" multimedia packet. + * + * @param session The MXit session object + * @param mxitId The username who's avatar to request + * @param avatarId The id of the avatar image (as string) + * @param data The avatar data + * @param buflen The length of the avatar data + */ +void mxit_get_avatar( struct MXitSession* session, const char* mxitId, const char* avatarId ) +{ + char data[CP_MAX_PACKET]; + int datalen = 0; + struct raw_chunk* chunk; + int size; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_get_avatar: %s\n", mxitId ); + + /* convert the packet to a byte stream */ + datalen = sprintf( data, "ms=" ); + + /* map chunk header over data buffer */ + chunk = (struct raw_chunk *) &data[datalen]; + + size = mxit_chunk_create_get_avatar( chunk->data, mxitId, avatarId, MXIT_AVATAR_SIZE ); + if ( size < 0 ) { + purple_debug_error( MXIT_PLUGIN_ID, "Error creating get avatar chunk (%i)\n", size ); + return; + } + + chunk->type = CP_CHUNK_GET_AVATAR; + chunk->length = htonl( size ); + datalen += sizeof( struct raw_chunk ) + size; + + /* send the byte stream to the mxit server */ + mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); +} + + +/*------------------------------------------------------------------------ + * Process a login message packet. + * + * @param session The MXit session object + * @param records The packet's data records + * @param rcount The number of data records + */ +static void mxit_parse_cmd_login( struct MXitSession* session, struct record** records, int rcount ) +{ + PurpleStatus* status; + int presence; + const char* profilelist[] = { CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_HIDENUMBER, CP_PROFILE_FULLNAME, + CP_PROFILE_TITLE, CP_PROFILE_FIRSTNAME, CP_PROFILE_LASTNAME, CP_PROFILE_EMAIL, + CP_PROFILE_MOBILENR }; + + purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN ); + + /* we were not yet logged in so we need to complete the login sequence here */ + session->flags |= MXIT_FLAG_LOGGEDIN; + purple_connection_update_progress( session->con, _( "Successfully Logged In..." ), 3, 4 ); + purple_connection_set_state( session->con, PURPLE_CONNECTED ); + + /* display the current splash-screen */ + if ( splash_popup_enabled( session ) ) + splash_display( session ); + + /* update presence status */ + status = purple_account_get_active_status( session->acc ); + presence = mxit_convert_presence( purple_status_get_id( status ) ); + if ( presence != MXIT_PRESENCE_ONLINE ) { + /* when logging into MXit, your default presence is online. but with the UI, one can change + * the presence to whatever. in the case where its changed to a different presence setting + * we need to send an update to the server, otherwise the user's presence will be out of + * sync between the UI and MXit. + */ + mxit_send_presence( session, presence, purple_status_get_attr_string( status, "message" ) ); + } + + /* save extra info if this is a HTTP connection */ + if ( session->http ) { + /* save the http server to use for this session */ + g_strlcpy( session->http_server, records[1]->fields[3]->data, sizeof( session->http_server ) ); + + /* save the session id */ + session->http_sesid = atoi( records[0]->fields[0]->data ); + } + + /* retrieve our MXit profile */ + mxit_send_extprofile_request( session, NULL, ARRAY_SIZE( profilelist ), profilelist ); +} + + +/*------------------------------------------------------------------------ + * Process a received message packet. + * + * @param session The MXit session object + * @param records The packet's data records + * @param rcount The number of data records + */ +static void mxit_parse_cmd_message( struct MXitSession* session, struct record** records, int rcount ) +{ + struct RXMsgData* mx = NULL; + char* message = NULL; + int msglen = 0; + int msgflags = 0; + int msgtype = 0; + + if ( ( rcount == 1 ) || ( records[0]->fcount < 2 ) || ( records[1]->fcount == 0 ) || ( records[1]->fields[0]->len == 0 ) ) { + /* packet contains no message or an empty message */ + return; + } + + message = records[1]->fields[0]->data; + msglen = strlen( message ); + + /* strip off dummy domain */ + mxit_strip_domain( records[0]->fields[0]->data ); + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "Message received from '%s'\n", records[0]->fields[0]->data ); +#endif + + /* decode message flags (if any) */ + if ( records[0]->fcount >= 5 ) + msgflags = atoi( records[0]->fields[4]->data ); + msgtype = atoi( records[0]->fields[2]->data ); + + if ( msgflags & CP_MSG_ENCRYPTED ) { + /* this is an encrypted message. we do not currently support those so ignore it */ + PurpleBuddy* buddy; + const char* name; + char msg[128]; + + buddy = purple_find_buddy( session->acc, records[0]->fields[0]->data ); + if ( buddy ) + name = purple_buddy_get_alias( buddy ); + else + name = records[0]->fields[0]->data; + g_snprintf( msg, sizeof( msg ), "%s sent you an encrypted message, but it is not supported on this client.", name ); + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), _( msg ) ); + return; + } + + /* create and initialise new markup struct */ + mx = g_new0( struct RXMsgData, 1 ); + mx->msg = g_string_sized_new( msglen ); + mx->session = session; + mx->from = g_strdup( records[0]->fields[0]->data ); + mx->timestamp = atoi( records[0]->fields[1]->data ); + mx->got_img = FALSE; + mx->chatid = -1; + mx->img_count = 0; + + /* update list of active chats */ + if ( !find_active_chat( session->active_chats, mx->from ) ) { + session->active_chats = g_list_append( session->active_chats, g_strdup( mx->from ) ); + } + + if ( is_multimx_contact( session, mx->from ) ) { + /* this is a MultiMx chatroom message */ + multimx_message_received( mx, message, msglen, msgtype, msgflags ); + } + else { + mxit_parse_markup( mx, message, msglen, msgtype, msgflags ); + } + + /* we are now done parsing the message */ + mx->converted = TRUE; + if ( mx->img_count == 0 ) { + /* we have all the data we need for this message to be displayed now. */ + mxit_show_message( mx ); + } + else { + /* this means there are still images outstanding for this message and + * still need to wait for them before we can display the message. + * so the image received callback function will eventually display + * the message. */ + } +} + + +/*------------------------------------------------------------------------ + * Process a received subscription request packet. + * + * @param session The MXit session object + * @param records The packet's data records + * @param rcount The number of data records + */ +static void mxit_parse_cmd_new_sub( struct MXitSession* session, struct record** records, int rcount ) +{ + struct contact* contact; + struct record* rec; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_new_sub (%i recs)\n", rcount ); + + for ( i = 0; i < rcount; i++ ) { + rec = records[i]; + + if ( rec->fcount < 4 ) { + purple_debug_error( MXIT_PLUGIN_ID, "BAD SUBSCRIPTION RECORD! %i fields\n", rec->fcount ); + break; + } + + /* build up a new contact info struct */ + contact = g_new0( struct contact, 1 ); + + strcpy( contact->username, rec->fields[0]->data ); + mxit_strip_domain( contact->username ); /* remove dummy domain */ + strcpy( contact->alias, rec->fields[1]->data ); + contact->type = atoi( rec->fields[2]->data ); + + if ( rec->fcount >= 5 ) { + /* there is a personal invite message attached */ + contact->msg = strdup( rec->fields[4]->data ); + } + else + contact->msg = NULL; + + /* handle the subscription */ + if ( contact-> type == MXIT_TYPE_MULTIMX ) { /* subscription to a MultiMX room */ + char* creator = NULL; + + if ( rec->fcount >= 6 ) + creator = rec->fields[5]->data; + + multimx_invite( session, contact, creator ); + } + else + mxit_new_subscription( session, contact ); + } +} + + +/*------------------------------------------------------------------------ + * Process a received contact update packet. + * + * @param session The MXit session object + * @param records The packet's data records + * @param rcount The number of data records + */ +static void mxit_parse_cmd_contact( struct MXitSession* session, struct record** records, int rcount ) +{ + struct contact* contact = NULL; + struct record* rec; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_contact (%i recs)\n", rcount ); + + for ( i = 0; i < rcount; i++ ) { + rec = records[i]; + + if ( rec->fcount < 6 ) { + purple_debug_error( MXIT_PLUGIN_ID, "BAD CONTACT RECORD! %i fields\n", rec->fcount ); + break; + } + + /* build up a new contact info struct */ + contact = g_new0( struct contact, 1 ); + + strcpy( contact->groupname, rec->fields[0]->data ); + strcpy( contact->username, rec->fields[1]->data ); + mxit_strip_domain( contact->username ); /* remove dummy domain */ + strcpy( contact->alias, rec->fields[2]->data ); + + contact->presence = atoi( rec->fields[3]->data ); + contact->type = atoi( rec->fields[4]->data ); + contact->mood = atoi( rec->fields[5]->data ); + + if ( rec->fcount > 6 ) { + /* added in protocol 5.9.0 - flags & subtype */ + contact->flags = atoi( rec->fields[6]->data ); + contact->subtype = rec->fields[7]->data[0]; + } + + /* add the contact to the buddy list */ + if ( contact-> type == MXIT_TYPE_MULTIMX ) /* contact is a MultiMX room */ + multimx_created( session, contact ); + else + mxit_update_contact( session, contact ); + } + + if ( !( session->flags & MXIT_FLAG_FIRSTROSTER ) ) { + session->flags |= MXIT_FLAG_FIRSTROSTER; + mxit_update_blist( session ); + } +} + + +/*------------------------------------------------------------------------ + * Process a received presence update packet. + * + * @param session The MXit session object + * @param records The packet's data records + * @param rcount The number of data records + */ +static void mxit_parse_cmd_presence( struct MXitSession* session, struct record** records, int rcount ) +{ + struct record* rec; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_presence (%i recs)\n", rcount ); + + for ( i = 0; i < rcount; i++ ) { + rec = records[i]; + + if ( rec->fcount < 6 ) { + purple_debug_error( MXIT_PLUGIN_ID, "BAD PRESENCE RECORD! %i fields\n", rec->fcount ); + break; + } + + /* + * The format of the record is: + * contactAddressN\1presenceN\1\moodN\1customMoodN\1statusMsgN\1avatarIdN + */ + mxit_strip_domain( rec->fields[0]->data ); /* contactAddress */ + + mxit_update_buddy_presence( session, rec->fields[0]->data, atoi( rec->fields[1]->data ), atoi( rec->fields[2]->data ), + rec->fields[3]->data, rec->fields[4]->data, rec->fields[5]->data ); + } +} + + +/*------------------------------------------------------------------------ + * Process a received extended profile packet. + * + * @param session The MXit session object + * @param records The packet's data records + * @param rcount The number of data records + */ +static void mxit_parse_cmd_extprofile( struct MXitSession* session, struct record** records, int rcount ) +{ + const char* mxitId = records[0]->fields[0]->data; + struct MXitProfile* profile = NULL; + int count; + int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_extprofile: profile for '%s'\n", mxitId ); + + profile = g_new0( struct MXitProfile, 1 ); + + /* set the count for attributes */ + count = atoi( records[0]->fields[1]->data ); + + for ( i = 0; i < count; i++ ) { + char* fname; + char* fvalue; + char* fstatus; + int f = ( i * 3 ) + 2; + + fname = records[0]->fields[f]->data; /* field name */ + fvalue = records[0]->fields[f + 1]->data; /* field value */ + fstatus = records[0]->fields[f + 2]->data; /* field status */ + + /* first check the status on the returned attribute */ + if ( fstatus[0] != '0' ) { + /* error: attribute requested was NOT found */ + purple_debug_error( MXIT_PLUGIN_ID, "Bad profile status on attribute '%s' \n", fname ); + continue; + } + + if ( strcmp( CP_PROFILE_BIRTHDATE, fname ) == 0 ) { + /* birthdate */ + if ( records[0]->fields[f + 1]->len > 10 ) { + fvalue[10] = '\0'; + records[0]->fields[f + 1]->len = 10; + } + memcpy( profile->birthday, fvalue, records[0]->fields[f + 1]->len ); + } + else if ( strcmp( CP_PROFILE_GENDER, fname ) == 0 ) { + /* gender */ + profile->male = ( fvalue[0] == '1' ); + } + else if ( strcmp( CP_PROFILE_HIDENUMBER, fname ) == 0 ) { + /* hide number */ + profile->hidden = ( fvalue[0] == '1' ); + } + else if ( strcmp( CP_PROFILE_FULLNAME, fname ) == 0 ) { + /* nickname */ + g_strlcpy( profile->nickname, fvalue, sizeof( profile->nickname ) ); + } + else if ( strcmp( CP_PROFILE_AVATAR, fname ) == 0 ) { + /* avatar id, we just ingore it cause we dont need it */ + } + else if ( strcmp( CP_PROFILE_TITLE, fname ) == 0 ) { + /* title */ + g_strlcpy( profile->title, fvalue, sizeof( profile->title ) ); + } + else if ( strcmp( CP_PROFILE_FIRSTNAME, fname ) == 0 ) { + /* first name */ + g_strlcpy( profile->firstname, fvalue, sizeof( profile->firstname ) ); + } + else if ( strcmp( CP_PROFILE_LASTNAME, fname ) == 0 ) { + /* last name */ + g_strlcpy( profile->lastname, fvalue, sizeof( profile->lastname ) ); + } + else if ( strcmp( CP_PROFILE_EMAIL, fname ) == 0 ) { + /* email address */ + g_strlcpy( profile->email, fvalue, sizeof( profile->email ) ); + } + else if ( strcmp( CP_PROFILE_MOBILENR, fname ) == 0 ) { + /* mobile number */ + g_strlcpy( profile->mobilenr, fvalue, sizeof( profile->mobilenr ) ); + } + else { + /* invalid profile attribute */ + purple_debug_error( MXIT_PLUGIN_ID, "Invalid profile attribute received '%s' \n", fname ); + } + } + + if ( records[0]->fields[0]->len == 0 ) { + /* no MXit id provided, so this must be our own profile information */ + if ( session->profile ) + g_free( session->profile ); + session->profile = profile; + } + else { + /* display other user's profile */ + mxit_show_profile( session, mxitId, profile ); + + /* cleanup */ + g_free( profile ); + } +} + + +/*------------------------------------------------------------------------ + * Return the length of a multimedia chunk + * + * @return The actual chunk data length in bytes + */ +static int get_chunk_len( const char* chunkdata ) +{ + int* sizeptr; + + sizeptr = (int*) &chunkdata[1]; /* we skip the first byte (type field) */ + + return ntohl( *sizeptr ); +} + + +/*------------------------------------------------------------------------ + * Process a received multimedia packet. + * + * @param session The MXit session object + * @param records The packet's data records + * @param rcount The number of data records + */ +static void mxit_parse_cmd_media( struct MXitSession* session, struct record** records, int rcount ) +{ + char type; + int size; + + type = records[0]->fields[0]->data[0]; + size = get_chunk_len( records[0]->fields[0]->data ); + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_media (%i records) (%i bytes)\n", rcount, size ); + + /* supported chunked data types */ + switch ( type ) { + case CP_CHUNK_CUSTOM : /* custom resource */ + { + struct cr_chunk chunk; + + /* decode the chunked data */ + memset( &chunk, 0, sizeof( struct cr_chunk ) ); + mxit_chunk_parse_cr( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk ); + + purple_debug_info( MXIT_PLUGIN_ID, "chunk info id=%s handle=%s op=%i\n", chunk.id, chunk.handle, chunk.operation ); + + /* this is a splash-screen operation */ + if ( strcmp( chunk.handle, HANDLE_SPLASH2 ) == 0 ) { + if ( chunk.operation == CR_OP_UPDATE ) { /* update the splash-screen */ + struct splash_chunk *splash = chunk.resources->data; // TODO: Fix - assuming 1st resource is splash + gboolean clickable = ( g_list_length( chunk.resources ) > 1 ); // TODO: Fix - if 2 resources, then is clickable + + if ( splash != NULL ) + splash_update( session, chunk.id, splash->data, splash->datalen, clickable ); + } + else if ( chunk.operation == CR_OP_REMOVE ) /* remove the splash-screen */ + splash_remove( session ); + } + + /* cleanup custom resources */ + g_list_foreach( chunk.resources, (GFunc)g_free, NULL ); + + } + break; + + case CP_CHUNK_OFFER : /* file offer */ + { + struct offerfile_chunk chunk; + + /* decode the chunked data */ + memset( &chunk, 0, sizeof( struct offerfile_chunk ) ); + mxit_chunk_parse_offer( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk ); + + /* process the offer */ + mxit_xfer_rx_offer( session, chunk.username, chunk.filename, chunk.filesize, chunk.fileid ); + } + break; + + case CP_CHUNK_GET : /* get file response */ + { + struct getfile_chunk chunk; + + /* decode the chunked data */ + memset( &chunk, 0, sizeof( struct getfile_chunk ) ); + mxit_chunk_parse_get( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk ); + + /* process the getfile */ + mxit_xfer_rx_file( session, chunk.fileid, chunk.data, chunk.length ); + } + break; + + case CP_CHUNK_GET_AVATAR : /* get avatars */ + { + struct getavatar_chunk chunk; + + /* decode the chunked data */ + memset( &chunk, 0, sizeof ( struct getavatar_chunk ) ); + mxit_chunk_parse_get_avatar( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk ); + + /* update avatar image */ + if ( chunk.data ) { + purple_debug_info( MXIT_PLUGIN_ID, "updating avatar for contact '%s'\n", chunk.mxitid ); + purple_buddy_icons_set_for_user( session->acc, chunk.mxitid, g_memdup( chunk.data, chunk.length), chunk.length, chunk.avatarid ); + } + + } + break; + + case CP_CHUNK_SET_AVATAR : + /* this is a reply packet to a set avatar request. no action is required */ + break; + + case CP_CHUNK_DIRECT_SND : + /* this is a ack for a file send. no action is required */ + break; + + case CP_CHUNK_RECIEVED : + /* this is a ack for a file received. no action is required */ + break; + + default : + purple_debug_error( MXIT_PLUGIN_ID, "Unsupported chunked data packet type received (%i)\n", type ); + break; + } +} + + +/*------------------------------------------------------------------------ + * Handle a redirect sent from the MXit server. + * + * @param session The MXit session object + * @param url The redirect information + */ +static void mxit_perform_redirect( struct MXitSession* session, const char* url ) +{ + gchar** parts; + gchar** host; + int type; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s\n", url ); + + /* tokenize the URL string */ + parts = g_strsplit( url, ";", 0 ); + + /* Part 1: protocol://host:port */ + host = g_strsplit( parts[0], ":", 4 ); + if ( strcmp( host[0], "socket" ) == 0 ) { + /* redirect to a MXit socket proxy */ + g_strlcpy( session->server, &host[1][2], sizeof( session->server ) ); + session->port = atoi( host[2] ); + } + else { + purple_connection_error( session->con, _( "Cannot perform redirect using the specified protocol" ) ); + goto redirect_fail; + } + + /* Part 2: type of redirect */ + type = atoi( parts[1] ); + if ( type == CP_REDIRECT_PERMANENT ) { + /* permanent redirect, so save new MXit server and port */ + purple_account_set_string( session->acc, MXIT_CONFIG_SERVER_ADDR, session->server ); + purple_account_set_int( session->acc, MXIT_CONFIG_SERVER_PORT, session->port ); + } + + /* Part 3: message (optional) */ + if ( parts[2] != NULL ) + purple_connection_notice( session->con, parts[2] ); + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s redirect to %s:%i\n", + ( type == CP_REDIRECT_PERMANENT ) ? "Permanent" : "Temporary", session->server, session->port ); + + /* perform the re-connect to the new MXit server */ + mxit_reconnect( session ); + +redirect_fail: + g_strfreev( parts ); + g_strfreev( host ); +} + + +/*------------------------------------------------------------------------ + * Process a success response received from the MXit server. + * + * @param session The MXit session object + * @param packet The received packet + */ +static int process_success_response( struct MXitSession* session, struct rx_packet* packet ) +{ + /* ignore ping/poll packets */ + if ( ( packet->cmd != CP_CMD_PING ) && ( packet->cmd != CP_CMD_POLL ) ) + session->last_rx = time( NULL ); + + /* + * when we pass the packet records to the next level for parsing + * we minus 3 records because 1) the first record is the packet + * type 2) packet reply status 3) the last record is bogus + */ + + /* packet command */ + switch ( packet->cmd ) { + + case CP_CMD_REGISTER : + /* fall through, when registeration successful, MXit will auto login */ + case CP_CMD_LOGIN : + /* login response */ + if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) { + mxit_parse_cmd_login( session, &packet->records[2], packet->rcount - 3 ); + } + break; + + case CP_CMD_LOGOUT : + /* logout response */ + session->flags &= ~MXIT_FLAG_LOGGEDIN; + purple_account_disconnect( session->acc ); + + /* note: + * we do not prompt the user here for a reconnect, because this could be the user + * logging in with his phone. so we just disconnect the account otherwise + * mxit will start to bounce between the phone and pidgin. also could be a valid + * disconnect selected by the user. + */ + return -1; + + case CP_CMD_CONTACT : + /* contact update */ + mxit_parse_cmd_contact( session, &packet->records[2], packet->rcount - 3 ); + break; + + case CP_CMD_PRESENCE : + /* presence update */ + mxit_parse_cmd_presence(session, &packet->records[2], packet->rcount - 3 ); + break; + + case CP_CMD_RX_MSG : + /* incoming message (no bogus record) */ + mxit_parse_cmd_message( session, &packet->records[2], packet->rcount - 2 ); + break; + + case CP_CMD_NEW_SUB : + /* new subscription request */ + mxit_parse_cmd_new_sub( session, &packet->records[2], packet->rcount - 3 ); + break; + + case CP_CMD_MEDIA : + /* multi-media message */ + mxit_parse_cmd_media( session, &packet->records[2], packet->rcount - 2 ); + break; + + case CP_CMD_EXTPROFILE_GET : + /* profile update */ + mxit_parse_cmd_extprofile( session, &packet->records[2], packet->rcount - 2 ); + break; + + case CP_CMD_MOOD : + /* mood update */ + case CP_CMD_UPDATE : + /* update contact information */ + case CP_CMD_ALLOW : + /* allow subscription ack */ + case CP_CMD_DENY : + /* deny subscription ack */ + case CP_CMD_INVITE : + /* invite contact ack */ + case CP_CMD_REMOVE : + /* remove contact ack */ + case CP_CMD_TX_MSG : + /* outgoing message ack */ + case CP_CMD_STATUS : + /* presence update ack */ + case CP_CMD_GRPCHAT_CREATE : + /* create groupchat */ + case CP_CMD_GRPCHAT_INVITE : + /* groupchat invite */ + case CP_CMD_PING : + /* ping reply */ + case CP_CMD_POLL : + /* HTTP poll reply */ + case CP_CMD_EXTPROFILE_SET : + /* profile update */ + case CP_CMD_SPLASHCLICK : + /* splash-screen clickthrough */ + break; + + default : + /* unknown packet */ + purple_debug_error( MXIT_PLUGIN_ID, "Received unknown client packet (cmd = %i)\n", packet->cmd ); + } + + return 0; +} + + +/*------------------------------------------------------------------------ + * Process an error response received from the MXit server. + * + * @param session The MXit session object + * @param packet The received packet + */ +static int process_error_response( struct MXitSession* session, struct rx_packet* packet ) +{ + char errmsg[256]; + const char* errdesc; + + /* set the error description to be shown to the user */ + if ( packet->errmsg ) + errdesc = packet->errmsg; + else + errdesc = "An internal MXit server error occurred."; + + purple_debug_info( MXIT_PLUGIN_ID, "Error Reply %i:%s\n", packet->errcode, errdesc ); + + if ( packet->errcode == MXIT_ERRCODE_LOGGEDOUT ) { + /* we are not currently logged in, so we need to reconnect */ + purple_connection_error( session->con, _( errmsg ) ); + } + + /* packet command */ + switch ( packet->cmd ) { + + case CP_CMD_REGISTER : + case CP_CMD_LOGIN : + if ( packet->errcode == MXIT_ERRCODE_REDIRECT ) { + mxit_perform_redirect( session, packet->errmsg ); + return 0; + } + else { + sprintf( errmsg, "Login error: %s (%i)", errdesc, packet->errcode ); + purple_connection_error( session->con, _( errmsg ) ); + return -1; + } + case CP_CMD_LOGOUT : + sprintf( errmsg, "Logout error: %s (%i)", errdesc, packet->errcode ); + purple_connection_error_reason( session->con, PURPLE_CONNECTION_ERROR_NAME_IN_USE, _( errmsg ) ); + return -1; + case CP_CMD_CONTACT : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Error" ), _( errdesc ) ); + break; + case CP_CMD_RX_MSG : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), _( errdesc ) ); + break; + case CP_CMD_TX_MSG : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Sending Error" ), _( errdesc ) ); + break; + case CP_CMD_STATUS : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Status Error" ), _( errdesc ) ); + break; + case CP_CMD_MOOD : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Mood Error" ), _( errdesc ) ); + break; + case CP_CMD_KICK : + /* + * the MXit server sends this packet if we were idle for too long. + * to stop the server from closing this connection we need to resend + * the login packet. + */ + mxit_send_login( session ); + break; + case CP_CMD_INVITE : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Invitation Error" ), _( errdesc ) ); + break; + case CP_CMD_REMOVE : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Removal Error" ), _( errdesc ) ); + break; + case CP_CMD_ALLOW : + case CP_CMD_DENY : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Subscription Error" ), _( errdesc ) ); + break; + case CP_CMD_UPDATE : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Update Error" ), _( errdesc ) ); + break; + case CP_CMD_MEDIA : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "File Transfer Error" ), _( errdesc ) ); + break; + case CP_CMD_GRPCHAT_CREATE : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Cannot create MultiMx room" ), _( errdesc ) ); + break; + case CP_CMD_GRPCHAT_INVITE : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "MultiMx Invitation Error" ), _( errdesc ) ); + break; + case CP_CMD_EXTPROFILE_GET : + case CP_CMD_EXTPROFILE_SET : + mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Profile Error" ), _( errdesc ) ); + break; + case CP_CMD_SPLASHCLICK : + /* ignore error */ + break; + case CP_CMD_PING : + case CP_CMD_POLL : + break; + default : + mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Error" ), _( errdesc ) ); + break; + } + + return 0; +} + + +/*======================================================================================================================== + * Low-level Packet receive + */ + +#ifdef DEBUG_PROTOCOL +/*------------------------------------------------------------------------ + * Dump a received packet structure. + * + * @param p The received packet + */ +static void dump_packet( struct rx_packet* p ) +{ + struct record* r = NULL; + struct field* f = NULL; + int i; + int j; + + purple_debug_info( MXIT_PLUGIN_ID, "PACKET DUMP: (%i records)\n", p->rcount ); + + for ( i = 0; i < p->rcount; i++ ) { + r = p->records[i]; + purple_debug_info( MXIT_PLUGIN_ID, "RECORD: (%i fields)\n", r->fcount ); + + for ( j = 0; j < r->fcount; j++ ) { + f = r->fields[j]; + purple_debug_info( MXIT_PLUGIN_ID, "\tFIELD: (len=%i) '%s' \n", f->len, f->data ); + } + } +} +#endif + + +/*------------------------------------------------------------------------ + * Free up memory used by a packet structure. + * + * @param p The received packet + */ +static void free_rx_packet( struct rx_packet* p ) +{ + struct record* r = NULL; + struct field* f = NULL; + int i; + int j; + + for ( i = 0; i < p->rcount; i++ ) { + r = p->records[i]; + + for ( j = 0; j < r->fcount; j++ ) { + g_free( f ); + } + g_free( r->fields ); + g_free( r ); + } + g_free( p->records ); +} + + +/*------------------------------------------------------------------------ + * Add a new field to a record. + * + * @param r Parent record object + * @return The newly created field + */ +static struct field* add_field( struct record* r ) +{ + struct field* field; + + field = g_new0( struct field, 1 ); + + r->fields = realloc( r->fields, sizeof( struct field* ) * ( r->fcount + 1 ) ); + r->fields[r->fcount] = field; + r->fcount++; + + return field; +} + + +/*------------------------------------------------------------------------ + * Add a new record to a packet. + * + * @param p The packet object + * @return The newly created record + */ +static struct record* add_record( struct rx_packet* p ) +{ + struct record* rec; + + rec = g_new0( struct record, 1 ); + + p->records = realloc( p->records, sizeof( struct record* ) * ( p->rcount + 1 ) ); + p->records[p->rcount] = rec; + p->rcount++; + + return rec; +} + + +/*------------------------------------------------------------------------ + * Parse the received byte stream into a proper client protocol packet. + * + * @param session The MXit session object + * @return Success (0) or Failure (!0) + */ +int mxit_parse_packet( struct MXitSession* session ) +{ + struct rx_packet packet; + struct record* rec; + struct field* field; + gboolean pbreak; + unsigned int i; + int res = 0; + +#ifdef DEBUG_PROTOCOL + purple_debug_info( MXIT_PLUGIN_ID, "Received packet (%i bytes)\n", session->rx_i ); + dump_bytes( session, session->rx_dbuf, session->rx_i ); +#endif + + i = 0; + while ( i < session->rx_i ) { + + /* create first record and field */ + rec = NULL; + field = NULL; + memset( &packet, 0x00, sizeof( struct rx_packet ) ); + rec = add_record( &packet ); + pbreak = FALSE; + + /* break up the received packet into fields and records for easy parsing */ + while ( ( i < session->rx_i ) && ( !pbreak ) ) { + + switch ( session->rx_dbuf[i] ) { + case CP_SOCK_REC_TERM : + /* new record */ + if ( packet.rcount == 1 ) { + /* packet command */ + packet.cmd = atoi( packet.records[0]->fields[0]->data ); + } + else if ( packet.rcount == 2 ) { + /* special case: binary multimedia packets should not be parsed here */ + if ( packet.cmd == CP_CMD_MEDIA ) { + /* add the chunked to new record */ + rec = add_record( &packet ); + field = add_field( rec ); + field->data = &session->rx_dbuf[i + 1]; + field->len = session->rx_i - i; + /* now skip the binary data */ + res = get_chunk_len( field->data ); + /* determine if we have more packets */ + if ( res + 6 + i < session->rx_i ) { + /* we have more than one packet in this stream */ + i += res + 6; + pbreak = TRUE; + } + else { + i = session->rx_i; + } + } + } + else if ( !field ) { + field = add_field( rec ); + field->data = &session->rx_dbuf[i]; + } + session->rx_dbuf[i] = '\0'; + rec = add_record( &packet ); + field = NULL; + + break; + case CP_FLD_TERM : + /* new field */ + session->rx_dbuf[i] = '\0'; + if ( !field ) { + field = add_field( rec ); + field->data = &session->rx_dbuf[i]; + } + field = NULL; + break; + case CP_PKT_TERM : + /* packet is done! */ + session->rx_dbuf[i] = '\0'; + pbreak = TRUE; + break; + default : + /* skip non special characters */ + if ( !field ) { + field = add_field( rec ); + field->data = &session->rx_dbuf[i]; + } + field->len++; + break; + } + + i++; + } + + if ( packet.rcount < 2 ) { + /* bad packet */ + purple_connection_error( session->con, _( "Invalid packet received from MXit." ) ); + free_rx_packet( &packet ); + continue; + } + + session->rx_dbuf[session->rx_i] = '\0'; + packet.errcode = atoi( packet.records[1]->fields[0]->data ); + + purple_debug_info( MXIT_PLUGIN_ID, "Packet received CMD:%i (%i)\n", packet.cmd, packet.errcode ); +#ifdef DEBUG_PROTOCOL + /* debug */ + dump_packet( &packet ); +#endif + + /* reset the out ack */ + if ( session->outack == packet.cmd ) { + /* outstanding ack received from mxit server */ + session->outack = 0; + } + + /* check packet status */ + if ( packet.errcode != MXIT_ERRCODE_SUCCESS ) { + /* error reply! */ + if ( ( packet.records[1]->fcount > 1 ) && ( packet.records[1]->fields[1]->data ) ) + packet.errmsg = packet.records[1]->fields[1]->data; + else + packet.errmsg = NULL; + + res = process_error_response( session, &packet ); + } + else { + /* success reply! */ + res = process_success_response( session, &packet ); + } + + /* free up the packet resources */ + free_rx_packet( &packet ); + } + + if ( session->outack == 0 ) + mxit_manage_queue( session ); + + return res; +} + + +/*------------------------------------------------------------------------ + * Callback when data is received from the MXit server. + * + * @param user_data The MXit session object + * @param source The file-descriptor on which data was received + * @param cond Condition which caused the callback (PURPLE_INPUT_READ) + */ +void mxit_cb_rx( gpointer user_data, gint source, PurpleInputCondition cond ) +{ + struct MXitSession* session = (struct MXitSession*) user_data; + char ch; + int res; + int len; + + if ( session->rx_state == RX_STATE_RLEN ) { + /* we are reading in the packet length */ + len = read( session->fd, &ch, 1 ); + if ( len < 0 ) { + /* connection error */ + purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x01)" ) ); + return; + } + else if ( len == 0 ) { + /* connection closed */ + purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x02)" ) ); + return; + } + else { + /* byte read */ + if ( ch == CP_REC_TERM ) { + /* the end of the length record found */ + session->rx_lbuf[session->rx_i] = '\0'; + session->rx_res = atoi( &session->rx_lbuf[3] ); + if ( session->rx_res > CP_MAX_PACKET ) { + purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x03)" ) ); + } + session->rx_state = RX_STATE_DATA; + session->rx_i = 0; + } + else { + /* still part of the packet length record */ + session->rx_lbuf[session->rx_i] = ch; + session->rx_i++; + if ( session->rx_i >= sizeof( session->rx_lbuf ) ) { + /* malformed packet length record (too long) */ + purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x04)" ) ); + return; + } + } + } + } + else if ( session->rx_state == RX_STATE_DATA ) { + /* we are reading in the packet data */ + len = read( session->fd, &session->rx_dbuf[session->rx_i], session->rx_res ); + if ( len < 0 ) { + /* connection error */ + purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x05)" ) ); + return; + } + else if ( len == 0 ) { + /* connection closed */ + purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x06)" ) ); + return; + } + else { + /* data read */ + session->rx_i += len; + session->rx_res -= len; + + if ( session->rx_res == 0 ) { + /* ok, so now we have read in the whole packet */ + session->rx_state = RX_STATE_PROC; + } + } + } + + if ( session->rx_state == RX_STATE_PROC ) { + /* we have a full packet, which we now need to process */ + res = mxit_parse_packet( session ); + + if ( res == 0 ) { + /* we are still logged in */ + session->rx_state = RX_STATE_RLEN; + session->rx_res = 0; + session->rx_i = 0; + } + } +} + + +/*------------------------------------------------------------------------ + * Log the user off MXit and close the connection + * + * @param session The MXit session object + */ +void mxit_close_connection( struct MXitSession* session ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "mxit_close_connection\n" ); + + if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) { + /* we are already closed */ + return; + } + else if ( session->flags & MXIT_FLAG_LOGGEDIN ) { + /* we are currently logged in so we need to send a logout packet */ + if ( !session->http ) { + mxit_send_logout( session ); + } + session->flags &= ~MXIT_FLAG_LOGGEDIN; + } + session->flags &= ~MXIT_FLAG_CONNECTED; + + /* cancel outstanding HTTP request */ + if ( ( session->http ) && ( session->http_out_req ) ) { + purple_util_fetch_url_cancel( (PurpleUtilFetchUrlData*) session->http_out_req ); + session->http_out_req = NULL; + } + + /* remove the input cb function */ + if ( session->con->inpa ) { + purple_input_remove( session->con->inpa ); + session->con->inpa = 0; + } + + /* remove HTTP poll timer */ + if ( session->http_timer_id > 0 ) + purple_timeout_remove( session->http_timer_id ); + + /* remove queue manager timer */ + if ( session->q_timer > 0 ) + purple_timeout_remove( session->q_timer ); + + /* remove all groupchat rooms */ + while ( session->rooms != NULL ) { + struct multimx* multimx = (struct multimx *) session->rooms->data; + + session->rooms = g_list_remove( session->rooms, multimx ); + + free( multimx ); + } + g_list_free( session->rooms ); + session->rooms = NULL; + + /* remove all rx chats names */ + while ( session->active_chats != NULL ) { + char* chat = (char*) session->active_chats->data; + + session->active_chats = g_list_remove( session->active_chats, chat ); + + g_free( chat ); + } + g_list_free( session->active_chats ); + session->active_chats = NULL; + + /* free profile information */ + if ( session->profile ) + free( session->profile ); + + /* free custom emoticons */ + mxit_free_emoticon_cache( session ); + + /* free allocated memory */ + g_free( session->encpwd ); + session->encpwd = NULL; + + /* flush all the commands still in the queue */ + flush_queue( session ); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/protocol.h Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,304 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- MXit client protocol implementation -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_PROTO_H_ +#define _MXIT_PROTO_H_ + + +/* Client protocol constants */ +#define CP_SOCK_REC_TERM '\x00' /* socket record terminator */ +#define CP_HTTP_REC_TERM '\x26' /* http record terminator '&' */ +#define CP_FLD_TERM '\x01' /* field terminator */ +#define CP_PKT_TERM '\x02' /* packet terminator */ + + +#define CP_MAX_PACKET ( 1024 * 1024 ) /* maximum client protocol packet size (1 MiB) */ +#define CP_MAX_FILESIZE ( 150 * 1000 ) /* maximum client protocol file transfer size (150 KB) */ +#define MXIT_EMOTICON_SIZE 18 /* icon size for custom emoticons */ +#define CP_MAX_STATUS_MSG 250 /* maximum status message length (in characters) */ + +/* Avatars */ +#define MXIT_AVATAR_SIZE 96 /* default avatar image size 96x96 */ +#define MXIT_AVATAR_TYPE "PNG" /* request avatars in this file type (only a suggestion) */ +#define MXIT_AVATAR_BITDEPT 24 /* request avatars with this bit depth (only a suggestion) */ + +/* Protocol error codes */ +#define MXIT_ERRCODE_SUCCESS 0 +#define MXIT_ERRCODE_REDIRECT 16 +#define MXIT_ERRCODE_LOGGEDOUT 42 + +/* MXit client features */ +#define MXIT_CF_NONE 0x000000 +#define MXIT_CF_FORMS 0x000001 +#define MXIT_CF_FILE_TRANSFER 0x000002 +#define MXIT_CF_CAMERA 0x000004 +#define MXIT_CF_COMMANDS 0x000008 +#define MXIT_CF_SMS 0x000010 +#define MXIT_CF_FILE_ACCESS 0x000020 +#define MXIT_CF_MIDP2 0x000040 +#define MXIT_CF_SKINS 0x000080 +#define MXIT_CF_AUDIO 0x000100 +#define MXIT_CF_ENCRYPTION 0x000200 +#define MXIT_CF_VOICE_REC 0x000400 +#define MXIT_CF_VECTOR_GFX 0x000800 +#define MXIT_CF_IMAGES 0x001000 +#define MXIT_CF_MARKUP 0x002000 +#define MXIT_CF_VIBES 0x004000 +#define MXIT_CF_SELECT_CONTACT 0x008000 +#define MXIT_CF_CUSTOM_EMO 0x010000 +#define MXIT_CF_ALERT_PROFILES 0x020000 +#define MXIT_CF_EXT_MARKUP 0x040000 +#define MXIT_CF_PLAIN_PWD 0x080000 +#define MXIT_CF_NO_GATEWAYS 0x100000 + +/* Client features supported by this implementation */ +#define MXIT_CP_FEATURES ( MXIT_CF_FILE_TRANSFER | MXIT_CF_FILE_ACCESS | MXIT_CF_AUDIO | MXIT_CF_MARKUP | MXIT_CF_EXT_MARKUP | MXIT_CF_NO_GATEWAYS | MXIT_CF_IMAGES | MXIT_CF_COMMANDS | MXIT_CF_VIBES | MXIT_CF_MIDP2 ) + + +#define MXIT_PING_INTERVAL ( 5 * 60 ) /* ping the server after X seconds of being idle (5 minutes) */ +#define MXIT_ACK_TIMEOUT ( 30 ) /* timeout after waiting X seconds for an ack from the server (30 seconds) */ + +/* MXit client version */ +#define MXIT_CP_DISTCODE "P" /* client distribution code (magic, do not touch!) */ +#define MXIT_CP_RELEASE "5.9.0" /* client protocol release version supported */ +#define MXIT_CP_ARCH "Y" /* client architecture series (Y not for Yoda but for PC-client) */ +#define MXIT_CLIENT_ID "LP" /* client ID as specified by MXit */ +#define MXIT_CP_PLATFORM "PURPLE" /* client platform */ +#define MXIT_CP_VERSION MXIT_CP_DISTCODE"-"MXIT_CP_RELEASE"-"MXIT_CP_ARCH"-"MXIT_CP_PLATFORM + +/* set operating system name */ +#if defined( __APPLE__ ) +#define MXIT_CP_OS "apple" +#elif defined( _WIN32 ) +#define MXIT_CP_OS "windows" +#elif defined( __linux__ ) +#define MXIT_CP_OS "linux" +#else +#define MXIT_CP_OS "unknown" +#endif + +/* Client capabilities */ +#define MXIT_CP_CAP "utf8=true;cid="MXIT_CLIENT_ID + +/* Client settings */ +#define MAX_QUEUE_SIZE ( 1 << 4 ) /* tx queue size (16 packets) */ +#define MXIT_POPUP_WIN_NAME "MXit Notification" /* popup window name */ +#define MXIT_MAX_ATTRIBS 10 /* maximum profile attributes supported */ +#define MXIT_DEFAULT_LOCALE "en" /* default locale setting */ +#define MXIT_DEFAULT_LOC "planetpurple" /* the default location for registration */ + +/* Client protocol commands */ +#define CP_CMD_LOGIN 0x0001 /* (1) login */ +#define CP_CMD_LOGOUT 0x0002 /* (2) logout */ +#define CP_CMD_CONTACT 0x0003 /* (3) get contacts */ +#define CP_CMD_UPDATE 0x0005 /* (5) update contact information */ +#define CP_CMD_INVITE 0x0006 /* (6) subscribe to new contact */ +#define CP_CMD_PRESENCE 0x0007 /* (7) get presence */ +#define CP_CMD_REMOVE 0x0008 /* (8) remove contact */ +#define CP_CMD_RX_MSG 0x0009 /* (9) get new messages */ +#define CP_CMD_TX_MSG 0x000A /* (10) send new message */ +#define CP_CMD_REGISTER 0x000B /* (11) register */ +//#define CP_CMD_PROFILE_SET 0x000C /* (12) set profile (DEPRECATED see CP_CMD_EXTPROFILE_SET) */ +#define CP_CMD_POLL 0x0011 /* (17) poll the HTTP server for an update */ +//#define CP_CMD_PROFILE_GET 0x001A /* (26) get profile (DEPRECATED see CP_CMD_EXTPROFILE_GET) */ +#define CP_CMD_MEDIA 0x001B /* (27) get multimedia message */ +#define CP_CMD_SPLASHCLICK 0x001F /* (31) splash-screen clickthrough */ +#define CP_CMD_STATUS 0x0020 /* (32) set shown presence & status */ +#define CP_CMD_MOOD 0x0029 /* (41) set mood */ +#define CP_CMD_KICK 0x002B /* (43) login kick */ +#define CP_CMD_GRPCHAT_CREATE 0x002C /* (44) create new groupchat */ +#define CP_CMD_GRPCHAT_INVITE 0x002D /* (45) add new groupchat member */ +#define CP_CMD_NEW_SUB 0x0033 /* (51) get new subscription */ +#define CP_CMD_ALLOW 0x0034 /* (52) allow subscription */ +#define CP_CMD_DENY 0x0037 /* (55) deny subscription */ +#define CP_CMD_EXTPROFILE_GET 0x0039 /* (57) get extended profile */ +#define CP_CMD_EXTPROFILE_SET 0x003A /* (58) set extended profile */ +#define CP_CMD_PING 0x03E8 /* (1000) ping (keepalive) */ + +/* HTTP connection */ +#define MXIT_HTTP_POLL_MIN 7 /* minimum time between HTTP polls (seconds) */ +#define MXIT_HTTP_POLL_MAX ( 10 * 60 ) /* maximum time between HTTP polls (seconds) */ + +/* receiver states */ +#define RX_STATE_RLEN 0x01 /* reading packet length section */ +#define RX_STATE_DATA 0x02 /* reading packet data section */ +#define RX_STATE_PROC 0x03 /* process read data */ + +/* message flags */ +#define CP_MSG_ENCRYPTED 0x0010 /* message is encrypted */ +#define CP_MSG_MARKUP 0x0200 /* message may contain markup */ +#define CP_MSG_EMOTICON 0x0400 /* message may contain custom emoticons */ + +/* redirect types */ +#define CP_REDIRECT_PERMANENT 1 /* permanent redirect */ +#define CP_REDIRECT_TEMPORARY 2 /* temporary redirect */ + +/* message tx types */ +#define CP_MSGTYPE_NORMAL 0x01 /* normal message */ +#define CP_MSGTYPE_CHAT 0x02 /* chat message */ +#define CP_MSGTYPE_HEADLINE 0x03 /* headline message */ +#define CP_MSGTYPE_ERROR 0x04 /* error message */ +#define CP_MSGTYPE_GROUPCHAT 0x05 /* groupchat message */ +#define CP_MSGTYPE_FORM 0x06 /* mxit custom form */ +#define CP_MSGTYPE_COMMAND 0x07 /* mxit command */ + + +/* extended profile attribute fields */ +#define CP_PROFILE_BIRTHDATE "birthdate" /* Birthdate (String - ISO 8601 format) */ +#define CP_PROFILE_GENDER "gender" /* Gender (Boolean - 0=female, 1=male) */ +#define CP_PROFILE_HIDENUMBER "hidenumber" /* Hide Number (Boolean - 0=false, 1=true) */ +#define CP_PROFILE_FULLNAME "fullname" /* Fullname (UTF8 String) */ +#define CP_PROFILE_STATUS "statusmsg" /* Status Message (UTF8 String) */ +#define CP_PROFILE_PREVSTATUS "prevstatusmsgs" /* Previous Status Messages (UTF8 String) */ +#define CP_PROFILE_AVATAR "avatarid" /* Avatar ID (String) */ +#define CP_PROFILE_MODIFIED "lastmodified" /* Last-Modified timestamp */ +#define CP_PROFILE_TITLE "title" /* Title (UTF8 String) */ +#define CP_PROFILE_FIRSTNAME "firstname" /* First name (UTF8 String) */ +#define CP_PROFILE_LASTNAME "lastname" /* Last name (UTF8 String) */ +#define CP_PROFILE_EMAIL "email" /* Email address (UTF8 String) */ +#define CP_PROFILE_MOBILENR "mobilenumber" /* Mobile Number (UTF8 String) */ + +/* extended profile field types */ +#define CP_PROF_TYPE_BOOL 0x02 /* boolean profile attribute type */ +#define CP_PROF_TYPE_INT 0x05 /* integer profile attribute type */ +#define CP_PROF_TYPE_UTF8 0x0A /* UTF8 string profile attribute type */ +#define CP_PROF_TYPE_DATE 0x0B /* date-time profile attribute type */ + + +/* define this to enable protocol debugging (very verbose logging) */ +#define DEBUG_PROTOCOL + + +/* ======================================================================================= */ + +struct MXitSession; + +/*------------------------------------------*/ + +struct field { + char* data; + int len; +}; + +struct record { + struct field** fields; + int fcount; +}; + +struct rx_packet { + int cmd; + int errcode; + char* errmsg; + struct record** records; + int rcount; +}; + +struct tx_packet { + int cmd; + char header[256]; + int headerlen; + char* data; + int datalen; +}; + +/*------------------------------------------*/ + + +/* + * A received message data object + */ +struct RXMsgData { + struct MXitSession* session; /* MXit session object */ + char* from; /* the sender's name */ + time_t timestamp; /* time at which the message was sent */ + GString* msg; /* newly created message converted to libPurple formatting */ + gboolean got_img; /* flag to say if this message got any images/emoticons embedded */ + short img_count; /* the amount of images/emoticons still outstanding for the message */ + int chatid; /* multimx chatroom id */ + int flags; /* libPurple conversation flags */ + gboolean converted; /* true if the message has been completely parsed and converted to libPurple markup */ + gboolean processed; /* the message has been processed completely and should be freed up */ +}; + + + +/* + * The packet transmission queue. + */ +struct tx_queue { + struct tx_packet* packets[MAX_QUEUE_SIZE]; /* array of packet pointers */ + int count; /* number of packets queued */ + int rd_i; /* queue current read index (queue offset for reading a packet) */ + int wr_i; /* queue current write index (queue offset for adding new packet) */ +}; + + +/* ======================================================================================= */ + +void mxit_popup( int type, const char* heading, const char* message ); +void mxit_strip_domain( char* username ); +gboolean find_active_chat( const GList* chats, const char* who ); + +void mxit_cb_rx( gpointer data, gint source, PurpleInputCondition cond ); +gboolean mxit_manage_queue( gpointer user_data ); +gboolean mxit_manage_polling( gpointer user_data ); + +void mxit_send_register( struct MXitSession* session ); +void mxit_send_login( struct MXitSession* session ); +void mxit_send_logout( struct MXitSession* session ); +void mxit_send_ping( struct MXitSession* session ); +void mxit_send_poll( struct MXitSession* session ); + +void mxit_send_presence( struct MXitSession* session, int presence, const char* statusmsg ); +void mxit_send_mood( struct MXitSession* session, int mood ); +void mxit_send_message( struct MXitSession* session, const char* to, const char* msg, gboolean parse_markup ); + +void mxit_send_extprofile_update( struct MXitSession* session, const char* password, unsigned int nr_attrib, const char* attributes ); +void mxit_send_extprofile_request( struct MXitSession* session, const char* username, unsigned int nr_attrib, const char* attribute[] ); + +void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname ); +void mxit_send_remove( struct MXitSession* session, const char* username ); +void mxit_send_allow_sub( struct MXitSession* session, const char* username, const char* alias ); +void mxit_send_deny_sub( struct MXitSession* session, const char* username ); +void mxit_send_update_contact( struct MXitSession* session, const char* username, const char* alias, const char* groupname ); +void mxit_send_splashclick( struct MXitSession* session, const char* splashid ); + +void mxit_send_file( struct MXitSession* session, const char* username, const char* filename, const unsigned char* buf, int buflen ); +void mxit_send_file_reject( struct MXitSession* session, const char* fileid ); +void mxit_send_file_accept( struct MXitSession* session, const char* fileid, int filesize, int offset ); +void mxit_send_file_received( struct MXitSession* session, const char* fileid, short status ); +void mxit_set_avatar( struct MXitSession* session, const unsigned char* avatar, int avatarlen ); +void mxit_get_avatar( struct MXitSession* session, const char* mxitId, const char* avatarId ); + +void mxit_send_groupchat_create( struct MXitSession* session, const char* groupname, int nr_usernames, const char* usernames[] ); +void mxit_send_groupchat_invite( struct MXitSession* session, const char* roomid, int nr_usernames, const char* usernames[] ); + +int mxit_parse_packet( struct MXitSession* session ); +void dump_bytes( struct MXitSession* session, const char* buf, int len ); +void mxit_close_connection( struct MXitSession* session ); + + +#endif /* _MXIT_PROTO_H_ */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/roster.c Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,722 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user roster management (mxit contacts) -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#include "purple.h" + +#include "protocol.h" +#include "mxit.h" +#include "roster.h" + + +struct contact_invite { + struct MXitSession* session; /* MXit session object */ + struct contact* contact; /* The contact performing the invite */ +}; + + +/*======================================================================================================================== + * Presence / Status + */ + +/* statuses (reference: libpurple/status.h) */ +static struct status +{ + PurpleStatusPrimitive primative; + int mxit; + const char* id; + const char* name; +} const mxit_statuses[] = { + /* primative, no, id, name */ + { PURPLE_STATUS_OFFLINE, MXIT_PRESENCE_OFFLINE, "offline", "Offline" }, /* 0 */ + { PURPLE_STATUS_AVAILABLE, MXIT_PRESENCE_ONLINE, "online", "Available" }, /* 1 */ + { PURPLE_STATUS_AWAY, MXIT_PRESENCE_AWAY, "away", "Away" }, /* 2 */ + { PURPLE_STATUS_AVAILABLE, MXIT_PRESENCE_AVAILABLE, "chat", "Chatty" }, /* 3 */ + { PURPLE_STATUS_UNAVAILABLE, MXIT_PRESENCE_DND, "dnd", "Do Not Disturb" } /* 4 */ +}; + + +/*------------------------------------------------------------------------ + * Return list of supported statuses. (see status.h) + * + * @param account The MXit account object + * @return List of PurpleStatusType + */ +GList* mxit_status_types( PurpleAccount* account ) +{ + GList* statuslist = NULL; + PurpleStatusType* type; + unsigned int i; + + for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) { + const struct status* status = &mxit_statuses[i]; + + /* add mxit status (reference: "libpurple/status.h") */ + type = purple_status_type_new_with_attrs( status->primative, status->id, status->name, TRUE, TRUE, FALSE, + "message", _( "Message" ), purple_value_new( PURPLE_TYPE_STRING ), + NULL ); + + statuslist = g_list_append( statuslist, type ); + } + + return statuslist; +} + + +/*------------------------------------------------------------------------ + * Returns the MXit presence code, given the unique status ID. + * + * @param id The status ID + * @return The MXit presence code + */ +int mxit_convert_presence( const char* id ) +{ + unsigned int i; + + for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) { + if ( strcmp( mxit_statuses[i].id, id ) == 0 ) /* status found! */ + return mxit_statuses[i].mxit; + } + + return -1; +} + + +/*------------------------------------------------------------------------ + * Returns the MXit presence as a string, given the MXit presence ID. + * + * @param no The MXit presence I (see above) + * @return The presence as a text string + */ +const char* mxit_convert_presence_to_name( short no ) +{ + unsigned int i; + + for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) { + if ( mxit_statuses[i].mxit == no ) /* status found! */ + return _( mxit_statuses[i].name ); + } + + return ""; +} + + +/*======================================================================================================================== + * Moods + */ + +/*------------------------------------------------------------------------ + * Returns the MXit mood as a string, given the MXit mood's ID. + * + * @param id The MXit mood ID (see roster.h) + * @return The mood as a text string + */ +const char* mxit_convert_mood_to_name( short id ) +{ + switch ( id ) { + case MXIT_MOOD_ANGRY : + return _( "Angry" ); + case MXIT_MOOD_EXCITED : + return _( "Excited" ); + case MXIT_MOOD_GRUMPY : + return _( "Grumpy" ); + case MXIT_MOOD_HAPPY : + return _( "Happy" ); + case MXIT_MOOD_INLOVE : + return _( "In Love" ); + case MXIT_MOOD_INVINCIBLE : + return _( "Invincible" ); + case MXIT_MOOD_SAD : + return _( "Sad" ); + case MXIT_MOOD_HOT : + return _( "Hot" ); + case MXIT_MOOD_SICK : + return _( "Sick" ); + case MXIT_MOOD_SLEEPY : + return _( "Sleepy" ); + case MXIT_MOOD_NONE : + default : + return ""; + } +} + + +/*======================================================================================================================== + * Subscription Types + */ + +/*------------------------------------------------------------------------ + * Returns a Contact subscription type as a string. + * + * @param subtype The subscription type + * @return The subscription type as a text string + */ +const char* mxit_convert_subtype_to_name( short subtype ) +{ + switch ( subtype ) { + case MXIT_SUBTYPE_BOTH : + return _( "Both" ); + case MXIT_SUBTYPE_PENDING : + return _( "Pending" ); + case MXIT_SUBTYPE_ASK : + return _( "Invited" ); + case MXIT_SUBTYPE_REJECTED : + return _( "Rejected" ); + case MXIT_SUBTYPE_DELETED : + return _( "Deleted" ); + case MXIT_SUBTYPE_NONE : + return _( "None" ); + default : + return ""; + } +} + + +/*======================================================================================================================== + * Calls from the MXit Protocol layer + */ + +#if 0 +/*------------------------------------------------------------------------ + * Dump a contact's info the the debug console. + * + * @param contact The contact + */ +static void dump_contact( struct contact* contact ) +{ + purple_debug_info( MXIT_PLUGIN_ID, "CONTACT: name='%s', alias='%s', group='%s', type='%i', presence='%i', mood='%i'\n", + contact->username, contact->alias, contact->groupname, contact->type, contact->presence, contact->mood ); +} +#endif + + +#if 0 +/*------------------------------------------------------------------------ + * Move a buddy from one group to another + * + * @param buddy the buddy to move between groups + * @param group the new group to move the buddy to + */ +static PurpleBuddy* mxit_update_buddy_group( struct MXitSession* session, PurpleBuddy* buddy, PurpleGroup* group ) +{ + struct contact* contact = NULL; + PurpleGroup* current_group = purple_buddy_get_group( buddy ); + PurpleBuddy* newbuddy = NULL; + + /* make sure the groups actually differs */ + if ( strcmp( current_group->name, group->name ) != 0 ) { + /* groupnames does not match, so we need to make the update */ + + purple_debug_info( MXIT_PLUGIN_ID, "Moving '%s' from group '%s' to '%s'\n", buddy->alias, current_group->name, group->name ); + + /* + * XXX: libPurple does not currently provide an API to change or rename the group name + * for a specific buddy. One option is to remove the buddy from the list and re-adding + * him in the new group, but by doing that makes the buddy go offline and then online + * again. This is really not ideal and very iretating, but how else then? + */ + + /* create new buddy */ + newbuddy = purple_buddy_new( session->acc, buddy->name, buddy->alias ); + newbuddy->proto_data = buddy->proto_data; + buddy->proto_data = NULL; + + /* remove the buddy */ + purple_blist_remove_buddy( buddy ); + + /* add buddy */ + purple_blist_add_buddy( newbuddy, NULL, group, NULL ); + + /* now re-instate his presence again */ + contact = newbuddy->proto_data; + if ( contact ) { + + /* update the buddy's status (reference: "libpurple/prpl.h") */ + if ( contact->statusMsg ) + purple_prpl_got_user_status( session->acc, newbuddy->name, mxit_statuses[contact->presence].id, "message", contact->statusMsg, NULL ); + else + purple_prpl_got_user_status( session->acc, newbuddy->name, mxit_statuses[contact->presence].id, NULL ); + + /* update avatar */ + if ( contact->avatarId ) { + mxit_get_avatar( session, newbuddy->name, contact->avatarId ); + g_free( contact->avatarId ); + contact->avatarId = NULL; + } + } + + return newbuddy; + } + else + return buddy; +} +#endif + + +/*------------------------------------------------------------------------ + * A contact update packet was received from the MXit server, so update the buddy's + * information. + * + * @param session The MXit session object + * @param contact The contact + */ +void mxit_update_contact( struct MXitSession* session, struct contact* contact ) +{ + PurpleBuddy* buddy = NULL; + PurpleGroup* group = NULL; + const char* id = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_contact: user='%s' alias='%s' group='%s'\n", contact->username, contact->alias, contact->groupname ); + + /* + * libPurple requires all contacts to be in a group. + * So if this MXit contact isn't in a group, pretend it is. + */ + if ( *contact->groupname == '\0' ) { + strcpy( contact->groupname, MXIT_DEFAULT_GROUP ); + } + + /* find or create a group for this contact */ + group = purple_find_group( contact->groupname ); + if ( !group ) + group = purple_group_new( contact->groupname ); + + /* see if the buddy is not in the group already */ + buddy = purple_find_buddy_in_group( session->acc, contact->username, group ); + if ( !buddy ) { + /* buddy not found in the group */ + + /* lets try finding him in all groups */ + buddy = purple_find_buddy( session->acc, contact->username ); + if ( buddy ) { + /* ok, so we found him in another group. to switch him between groups we must delete him and add him again. */ + purple_blist_remove_buddy( buddy ); + buddy = NULL; + } + + /* create new buddy */ + buddy = purple_buddy_new( session->acc, contact->username, contact->alias ); + buddy->proto_data = contact; + + /* add new buddy to list */ + purple_blist_add_buddy( buddy, NULL, group, NULL ); + } + else { + /* buddy was found in the group */ + + /* now update the buddy's alias */ + purple_blist_alias_buddy( buddy, contact->alias ); + + /* replace the buddy's contact struct */ + if ( buddy->proto_data ) + free( buddy->proto_data ); + buddy->proto_data = contact; + } + + /* load buddy's avatar id */ + id = purple_buddy_icons_get_checksum_for_user( buddy ); + if ( id ) + contact->avatarId = g_strdup( id ); + else + contact->avatarId = NULL; + + /* update the buddy's status (reference: "libpurple/prpl.h") */ + purple_prpl_got_user_status( session->acc, contact->username, mxit_statuses[contact->presence].id, NULL ); +} + + +/*------------------------------------------------------------------------ + * A presence update packet was received from the MXit server, so update the buddy's + * information. + * + * @param session The MXit session object + * @param username The contact which presence to update + * @param presence The new presence state for the contact + * @param mood The new mood for the contact + * @param customMood The custom mood identifier + * @param statusMsg This is the contact's status message + * @param avatarId This is the contact's avatar id + */ +void mxit_update_buddy_presence( struct MXitSession* session, const char* username, short presence, short mood, const char* customMood, const char* statusMsg, const char* avatarId ) +{ + PurpleBuddy* buddy = NULL; + struct contact* contact = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: user='%s' presence=%i mood=%i customMood='%s' statusMsg='%s' avatar='%s'\n", + username, presence, mood, customMood, statusMsg, avatarId ); + + if ( ( presence < MXIT_PRESENCE_OFFLINE ) || ( presence > MXIT_PRESENCE_DND ) ) { + purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: invalid presence state %i\n", presence ); + return; /* ignore packet */ + } + + /* find the buddy information for this contact (reference: "libpurple/blist.h") */ + buddy = purple_find_buddy( session->acc, username ); + if ( !buddy ) { + purple_debug_warning( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: unable to find the buddy '%s'\n", username ); + return; + } + + contact = buddy->proto_data; + if ( !contact ) + return; + + contact->presence = presence; + contact->mood = mood; + + g_strlcpy( contact->customMood, customMood, sizeof( contact->customMood ) ); + // TODO: Download custom mood frame. + + /* update status message */ + if ( contact->statusMsg ) { + g_free( contact->statusMsg ); + contact->statusMsg = NULL; + } + if ( statusMsg[0] != '\0' ) + contact->statusMsg = g_strdup( statusMsg ); + + /* update avatarId */ + if ( ( contact->avatarId ) && ( g_ascii_strcasecmp( contact->avatarId, avatarId ) == 0 ) ) { + /* avatar has not changed - do nothing */ + } + else if ( avatarId[0] != '\0' ) { /* avatar has changed */ + if ( contact->avatarId ) + g_free( contact->avatarId ); + contact->avatarId = g_strdup( avatarId ); + + /* Send request to download new avatar image */ + mxit_get_avatar( session, username, avatarId ); + } + else /* clear current avatar */ + purple_buddy_icons_set_for_user( session->acc, username, NULL, 0, NULL ); + + /* update the buddy's status (reference: "libpurple/prpl.h") */ + if ( contact->statusMsg ) + purple_prpl_got_user_status( session->acc, username, mxit_statuses[contact->presence].id, "message", contact->statusMsg, NULL ); + else + purple_prpl_got_user_status( session->acc, username, mxit_statuses[contact->presence].id, NULL ); +} + + +/*------------------------------------------------------------------------ + * update the blist cached by libPurple. We need to do this to keep + * libPurple and MXit's rosters in sync with each other. + * + * @param session The MXit session object + */ +void mxit_update_blist( struct MXitSession* session ) +{ + PurpleBuddy* buddy = NULL; + GSList* list = NULL; + unsigned int i; + + /* remove all buddies we did not receive a roster update for. + * these contacts must have been removed from another client */ + list = purple_find_buddies( session->acc, NULL ); + + for ( i = 0; i < g_slist_length( list ); i++ ) { + buddy = g_slist_nth_data( list, i ); + + if ( !buddy->proto_data ) { + /* this buddy should be removed, because we did not receive him in our roster update from MXit */ + purple_debug_info( MXIT_PLUGIN_ID, "Removed 'old' buddy from the blist '%s' (%s)\n", buddy->alias, buddy->name ); + purple_blist_remove_buddy( buddy ); + } + } + + /* tell the UI to update the blist */ + purple_blist_add_account( session->acc ); +} + + +/*------------------------------------------------------------------------ + * The user authorized an invite (subscription request). + * + * @param user_data Object associated with the invite + */ +static void mxit_cb_buddy_auth( gpointer user_data ) +{ + struct contact_invite* invite = (struct contact_invite*) user_data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_buddy_auth '%s'\n", invite->contact->username ); + + /* send a allow subscription packet to MXit */ + mxit_send_allow_sub( invite->session, invite->contact->username, invite->contact->alias ); + + /* freeup invite object */ + if ( invite->contact->msg ) + g_free( invite->contact->msg ); + g_free( invite->contact ); + g_free( invite ); +} + + +/*------------------------------------------------------------------------ + * The user rejected an invite (subscription request). + * + * @param user_data Object associated with the invite + */ +static void mxit_cb_buddy_deny( gpointer user_data ) +{ + struct contact_invite* invite = (struct contact_invite*) user_data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_buddy_deny '%s'\n", invite->contact->username ); + + /* send a deny subscription packet to MXit */ + mxit_send_deny_sub( invite->session, invite->contact->username ); + + /* freeup invite object */ + if ( invite->contact->msg ) + g_free( invite->contact->msg ); + g_free( invite->contact ); + g_free( invite ); +} + + +/*------------------------------------------------------------------------ + * A new subscription request packet was received from the MXit server. + * Prompt user to accept or reject it. + * + * @param session The MXit session object + * @param contact The contact performing the invite + */ +void mxit_new_subscription( struct MXitSession* session, struct contact* contact ) +{ + struct contact_invite* invite; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_new_subscription from '%s' (%s)\n", contact->username, contact->alias ); + + invite = g_new0( struct contact_invite, 1 ); + invite->session = session; + invite->contact = contact; + + /* (reference: "libpurple/account.h") */ + purple_account_request_authorization( session->acc, contact->username, NULL, contact->alias, contact->msg, FALSE, mxit_cb_buddy_auth, mxit_cb_buddy_deny, invite ); +} + + +/*------------------------------------------------------------------------ + * Return TRUE if this is a MXit Chatroom contact. + * + * @param session The MXit session object + * @param username The username of the contact + */ +gboolean is_mxit_chatroom_contact( struct MXitSession* session, const char* username ) +{ + PurpleBuddy* buddy; + struct contact* contact = NULL; + + /* find the buddy */ + buddy = purple_find_buddy( session->acc, username ); + if ( !buddy ) { + purple_debug_warning( MXIT_PLUGIN_ID, "is_mxit_chatroom_contact: unable to find the buddy '%s'\n", username ); + return FALSE; + } + + contact = buddy->proto_data; + if ( !contact ) + return FALSE; + + return ( contact->type == MXIT_TYPE_CHATROOM ); +} + + +/*======================================================================================================================== + * Callbacks from libpurple + */ + +/*------------------------------------------------------------------------ + * The user has added a buddy to the list, so send an invite request. + * + * @param gc The connection object + * @param buddy The new buddy + * @param group The group of the new buddy + */ +void mxit_add_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + GSList* list = NULL; + PurpleBuddy* mxbuddy = NULL; + unsigned int i; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy '%s' (group='%s')\n", buddy->name, group->name ); + + list = purple_find_buddies( session->acc, buddy->name ); + if ( g_slist_length( list ) == 1 ) { + purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy (scenario 1) (list:%i)\n", g_slist_length( list ) ); + /* + * we only send an invite to MXit when the user is not already inside our + * blist. this is done because purple does an add_buddy() call when + * you accept an invite. so in that case the user is already + * in our blist and ready to be chatted to. + */ + mxit_send_invite( session, buddy->name, buddy->alias, group->name ); + } + else { + purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy (scenario 2) (list:%i)\n", g_slist_length( list ) ); + /* + * we already have the buddy in our list, so we will only update + * his information here and not send another invite message + */ + + /* find the correct buddy */ + for ( i = 0; i < g_slist_length( list ); i++ ) { + mxbuddy = g_slist_nth_data( list, i ); + + if ( mxbuddy->proto_data != NULL ) { + /* this is our REAL MXit buddy! */ + + /* now update the buddy's alias */ + purple_blist_alias_buddy( mxbuddy, buddy->alias ); + + /* now update the buddy's group */ +// mxbuddy = mxit_update_buddy_group( session, mxbuddy, group ); + + /* send the update to the MXit server */ + mxit_send_update_contact( session, mxbuddy->name, mxbuddy->alias, group->name ); + } + } + } + + /* + * we remove the buddy here from the buddy list because the MXit server + * will send us a proper contact update packet if this succeeds. now + * we do not have to worry about error handling in case of adding an + * invalid contact. so the user will still see the contact as offline + * until he eventually accepts the invite. + */ + purple_blist_remove_buddy( buddy ); + + g_slist_free( list ); +} + + +/*------------------------------------------------------------------------ + * The user has removed a buddy from the list. + * + * @param gc The connection object + * @param buddy The buddy being removed + * @param group The group the buddy was in + */ +void mxit_remove_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_remove_buddy '%s'\n", buddy->name ); + + mxit_send_remove( session, buddy->name ); +} + + +/*------------------------------------------------------------------------ + * The user changed the buddy's alias. + * + * @param gc The connection object + * @param who The username of the buddy + * @param alias The new alias + */ +void mxit_buddy_alias( PurpleConnection* gc, const char* who, const char* alias ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + PurpleBuddy* buddy = NULL; + PurpleGroup* group = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_buddy_alias '%s' to '%s\n", who, alias ); + + /* find the buddy */ + buddy = purple_find_buddy( session->acc, who ); + if ( !buddy ) { + purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_alias: unable to find the buddy '%s'\n", who ); + return; + } + + /* find buddy group */ + group = purple_buddy_get_group( buddy ); + if ( !group ) { + purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_alias: unable to find the group for buddy '%s'\n", who ); + return; + } + + mxit_send_update_contact( session, who, alias, group->name ); +} + + +/*------------------------------------------------------------------------ + * The user changed the group for a single buddy. + * + * @param gc The connection object + * @param who The username of the buddy + * @param old_group The old group's name + * @param new_group The new group's name + */ +void mxit_buddy_group( PurpleConnection* gc, const char* who, const char* old_group, const char* new_group ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + PurpleBuddy* buddy = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_buddy_group from '%s' to '%s'\n", old_group, new_group ); + + /* find the buddy */ + buddy = purple_find_buddy( session->acc, who ); + if ( !buddy ) { + purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_group: unable to find the buddy '%s'\n", who ); + return; + } + + mxit_send_update_contact( session, who, buddy->alias, new_group ); +} + + +/*------------------------------------------------------------------------ + * The user has selected to rename a group, so update all contacts in that + * group. + * + * @param gc The connection object + * @param old_name The old group name + * @param group The updated group object + * @param moved_buddies The buddies affected by the rename + */ +void mxit_rename_group( PurpleConnection* gc, const char* old_name, PurpleGroup* group, GList* moved_buddies ) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + PurpleBuddy* buddy = NULL; + GList* item = NULL; + + purple_debug_info( MXIT_PLUGIN_ID, "mxit_rename_group from '%s' to '%s\n", old_name, group->name ); + + // TODO: Might be more efficient to use the "rename group" command (cmd=29). + + /* loop through all the contacts in the group and send updates */ + item = moved_buddies; + while ( item ) { + buddy = item->data; + mxit_send_update_contact( session, buddy->name, buddy->alias, group->name ); + item = g_list_next( item ); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/roster.h Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,139 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- user roster management (mxit contacts) -- + * + * Pieter Loubser <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_ROSTER_H_ +#define _MXIT_ROSTER_H_ + + +/* MXit contact presence states */ +#define MXIT_PRESENCE_OFFLINE 0x00 +#define MXIT_PRESENCE_ONLINE 0x01 +#define MXIT_PRESENCE_AWAY 0x02 +#define MXIT_PRESENCE_AVAILABLE 0x03 +#define MXIT_PRESENCE_DND 0x04 + + +/* MXit contact types */ +#define MXIT_TYPE_MXIT 0x00 +#define MXIT_TYPE_JABBER 0x01 +#define MXIT_TYPE_MSN 0x02 +#define MXIT_TYPE_YAHOO 0x03 +#define MXIT_TYPE_ICQ 0x04 +#define MXIT_TYPE_AIM 0x05 +#define MXIT_TYPE_QQ 0x06 +#define MXIT_TYPE_WV 0x07 +#define MXIT_TYPE_BOT 0x08 +#define MXIT_TYPE_CHATROOM 0x09 +#define MXIT_TYPE_SMS 0x0A +#define MXIT_TYPE_GROUP 0x0B +#define MXIT_TYPE_GALLERY 0x0C +#define MXIT_TYPE_INFO 0x0D +#define MXIT_TYPE_MULTIMX 0x0E +#define MXIT_TYPE_HYBRID 0x0F + + +/* MXit contact moods */ +#define MXIT_MOOD_NONE 0x00 +#define MXIT_MOOD_ANGRY 0x01 +#define MXIT_MOOD_EXCITED 0x02 +#define MXIT_MOOD_GRUMPY 0x03 +#define MXIT_MOOD_HAPPY 0x04 +#define MXIT_MOOD_INLOVE 0x05 +#define MXIT_MOOD_INVINCIBLE 0x06 +#define MXIT_MOOD_SAD 0x07 +#define MXIT_MOOD_HOT 0x08 +#define MXIT_MOOD_SICK 0x09 +#define MXIT_MOOD_SLEEPY 0x0A + + +/* MXit contact flags */ +#define MXIT_CFLAG_HIDDEN 0x02 +#define MXIT_CFLAG_GATEWAY 0x04 +#define MXIT_CFLAG_FOCUS_SEND_BLANK 0x20000 + + +/* Subscription types */ +#define MXIT_SUBTYPE_BOTH 'B' +#define MXIT_SUBTYPE_PENDING 'P' +#define MXIT_SUBTYPE_ASK 'A' +#define MXIT_SUBTYPE_REJECTED 'R' +#define MXIT_SUBTYPE_DELETED 'D' +#define MXIT_SUBTYPE_NONE 'N' + + +/* client protocol constants */ +#define MXIT_CP_MAX_JID_LEN 64 +#define MXIT_CP_MAX_GROUP_LEN 32 +#define MXIT_CP_MAX_ALIAS_LEN 48 + +#define MXIT_DEFAULT_GROUP "MXit" + + +/* + * a MXit contact + */ +struct contact { + char username[MXIT_CP_MAX_JID_LEN+1]; /* unique contact name (with domain) */ + char alias[MXIT_CP_MAX_GROUP_LEN+1]; /* contact alias (what will be seen) */ + char groupname[MXIT_CP_MAX_ALIAS_LEN+1]; /* contact group name */ + + short type; /* contact type */ + short mood; /* contact current mood */ + int flags; /* contact flags */ + short presence; /* presence state */ + short subtype; /* subscription type */ + + char* msg; /* invite message */ + + char customMood[16]; /* custom mood */ + char* statusMsg; /* status message */ + char* avatarId; /* avatarId */ +}; + +/* Presence / Status */ +GList* mxit_status_types( PurpleAccount* account ); +int mxit_convert_presence( const char* id ); +const char* mxit_convert_presence_to_name( short no ); +const char* mxit_convert_subtype_to_name( short subtype ); + +/* Moods */ +const char* mxit_convert_mood_to_name( short id ); + +/* MXit Protocol callbacks */ +void mxit_update_contact( struct MXitSession* session, struct contact* contact ); +void mxit_update_buddy_presence( struct MXitSession* session, const char* username, short presence, short mood, const char* customMood, const char* statusMsg, const char* avatarId ); +void mxit_new_subscription( struct MXitSession* session, struct contact* contact ); +void mxit_update_blist( struct MXitSession* session ); +gboolean is_mxit_chatroom_contact( struct MXitSession* session, const char* username ); + +/* libPurple callbacks */ +void mxit_add_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group ); +void mxit_remove_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group ); +void mxit_buddy_alias( PurpleConnection* gc, const char* who, const char* alias ); +void mxit_buddy_group( PurpleConnection* gc, const char* who, const char* old_group, const char* new_group ); +void mxit_rename_group( PurpleConnection* gc, const char* old_name, PurpleGroup* group, GList* moved_buddies ); + + +#endif /* _MXIT_ROSTER_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/splashscreen.c Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,223 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- splash screens -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include <libgen.h> +#include <glib/gstdio.h> + +#include "purple.h" +#include "imgstore.h" + +#include "protocol.h" +#include "mxit.h" +#include "splashscreen.h" + + +/*------------------------------------------------------------------------ + * Return the ID of the current splash-screen. + * + * @param session The MXit session object + * @return The ID of the splash-screen (or NULL if no splash-screen) + */ +const char* splash_current(struct MXitSession* session) +{ + const char* splashId = purple_account_get_string(session->acc, MXIT_CONFIG_SPLASHID, NULL); + + purple_debug_info(MXIT_PLUGIN_ID, "Current splashId: '%s'\n", splashId); + + if ((splashId != NULL) && (*splashId != '\0')) + return splashId; + else + return NULL; +} + + +/*------------------------------------------------------------------------ + * Indicate if splash-screen popups are enabled. + * + * @param session The MXit session object + * @return TRUE if the popup is enabled. + */ +gboolean splash_popup_enabled(struct MXitSession* session) +{ + return purple_account_get_bool(session->acc, MXIT_CONFIG_SPLASHPOPUP, DEFAULT_SPLASH_POPUP); +} + + +/*------------------------------------------------------------------------ + * Return if the current splash-screen is clickable. + * + * @param session The MXit session object + * @return TRUE or FALSE + */ +static gboolean splash_clickable(struct MXitSession* session) +{ + return purple_account_get_bool(session->acc, MXIT_CONFIG_SPLASHCLICK, FALSE); +} + + +/*------------------------------------------------------------------------ + * Remove the stored splash-screen (if it exists). + * + * @param session The MXit session object + */ +void splash_remove(struct MXitSession* session) +{ + const char* splashId = NULL; + char* filename; + + /* Get current splash ID */ + splashId = splash_current(session); + + if (splashId != NULL) { + purple_debug_info(MXIT_PLUGIN_ID, "Removing splashId: '%s'\n", splashId); + + /* Delete stored splash image */ + filename = g_strdup_printf("%s/mxit/%s.png", purple_user_dir(), splashId); + g_unlink(filename); + g_free(filename); + + /* Clear current splash ID from settings */ + purple_account_set_string(session->acc, MXIT_CONFIG_SPLASHID, ""); + purple_account_set_bool(session->acc, MXIT_CONFIG_SPLASHCLICK, FALSE); + } +} + + +/*------------------------------------------------------------------------ + * Save a new splash-screen for later display. + * + * @param session The MXit session object + * @param splashID The ID of the splash-screen + * @param data Splash-screen image data (PNG format) + * @param datalen Splash-screen image data size + */ +void splash_update(struct MXitSession* session, const char* splashId, const char* data, int datalen, gboolean clickable) +{ + char* dir; + char* filename; + + /* Remove the current splash-screen */ + splash_remove(session); + + /* Save the new splash image */ + dir = g_strdup_printf("%s/mxit", purple_user_dir()); + purple_build_dir(dir, S_IRUSR | S_IWUSR | S_IXUSR); /* ensure directory exists */ + + filename = g_strdup_printf("%s/%s.png", dir, splashId); + if (purple_util_write_data_to_file_absolute(filename, data, datalen)) { + /* Store new splash-screen ID to settings */ + purple_account_set_string(session->acc, MXIT_CONFIG_SPLASHID, splashId); + purple_account_set_bool(session->acc, MXIT_CONFIG_SPLASHCLICK, clickable ); + } + + g_free(dir); + g_free(filename); +} + + +/*------------------------------------------------------------------------ + * The user has clicked OK on the Splash request form. + * + * @param gc The connection object + * @param fields The list of fields in the accepted form + */ +static void splash_click_ok(PurpleConnection* gc, PurpleRequestFields* fields) +{ + struct MXitSession* session = (struct MXitSession*) gc->proto_data; + const char* splashId; + + /* Get current splash ID */ + splashId = splash_current(session); + if (!splashId) + return; + + /* if is clickable, then send click event */ + if (splash_clickable(session)) + mxit_send_splashclick(session, splashId); +} + + +/*------------------------------------------------------------------------ + * Display the current splash-screen. + * + * @param session The MXit session object + */ +void splash_display(struct MXitSession* session) +{ + const char* splashId = NULL; + char* filename; + gchar* imgdata; + gsize imglen; + int imgid = -1; + + /* Get current splash ID */ + splashId = splash_current(session); + if (splashId == NULL) /* no splash-screen */ + return; + + purple_debug_info(MXIT_PLUGIN_ID, "Display Splash: '%s'\n", splashId); + + /* Load splash-screen image from file */ + filename = g_strdup_printf("%s/mxit/%s.png", purple_user_dir(), splashId); + if (g_file_get_contents(filename, &imgdata, &imglen, NULL)) { + char buf[128]; + + /* Add splash-image to imagestore */ + imgid = purple_imgstore_add_with_id(g_memdup(imgdata, imglen), imglen, NULL); + + /* Generate and display message */ + g_snprintf(buf, sizeof(buf), "<img id=\"%d\">", imgid); + + /* Open a request-type popup to display the image */ + { + PurpleRequestFields* fields; + PurpleRequestFieldGroup* group; + PurpleRequestField* field; + + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + + field = purple_request_field_image_new("splash", "", imgdata, imglen); /* add splash image */ + purple_request_field_group_add_field(group, field); + + if (splash_clickable(session)) { + purple_request_fields(session->con, _("MXit Advertising"), NULL, NULL, fields, + _("More Information"), G_CALLBACK(splash_click_ok), _("Close"), NULL, session->acc, NULL, NULL, session->con); + } + else { + purple_request_fields(session->con, _("MXit Advertising"), NULL, NULL, fields, + _("Continue"), G_CALLBACK(splash_click_ok), _("Close"), NULL, session->acc, NULL, NULL, session->con); + } + } + + /* Release reference to image */ + purple_imgstore_unref_by_id(imgid); + + g_free(imgdata); + } + + g_free(filename); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/mxit/splashscreen.h Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,59 @@ +/* + * MXit Protocol libPurple Plugin + * + * -- splash screens -- + * + * Andrew Victor <libpurple@mxit.com> + * + * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. + * <http://www.mxitlifestyle.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef _MXIT_SPLASHSCREEN_H_ +#define _MXIT_SPLASHSCREEN_H_ + +#define HANDLE_SPLASH1 "plas1.png" +#define HANDLE_SPLASH2 "plas2.png" + +#define DEFAULT_SPLASH_POPUP FALSE /* disabled by default */ + +/* + * Return the ID of the current splash-screen. + */ +const char* splash_current(struct MXitSession* session); + +/* + * Indicate if splash-screen popups are enabled. + */ +gboolean splash_popup_enabled(); + +/* + * Save a new splash-screen. + */ +void splash_update(struct MXitSession* session, const char* splashId, const char* data, int datalen, gboolean clickable); + +/* + * Remove the stored splash-screen (if it exists). + */ +void splash_remove(struct MXitSession* session); + +/* + * Display the current splash-screen. + */ +void splash_display(struct MXitSession* session); + +#endif /* _MXIT_SPLASHSCREEN_H_ */
--- a/libpurple/protocols/oscar/clientlogin.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/oscar/clientlogin.c Wed Nov 11 03:07:21 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",
--- a/libpurple/protocols/oscar/family_feedbag.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/oscar/family_feedbag.c Wed Nov 11 03:07:21 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) {
--- a/libpurple/protocols/oscar/family_icbm.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/oscar/family_icbm.c Wed Nov 11 03:07:21 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);
--- a/libpurple/protocols/oscar/family_oservice.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/oscar/family_oservice.c Wed Nov 11 03:07:21 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; }
--- a/libpurple/protocols/oscar/flap_connection.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/oscar/flap_connection.c Wed Nov 11 03:07:21 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; }
--- a/libpurple/protocols/oscar/libaim.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/oscar/libaim.c Wed Nov 11 03:07:21 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);
--- a/libpurple/protocols/oscar/libicq.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/oscar/libicq.c Wed Nov 11 03:07:21 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);
--- a/libpurple/protocols/oscar/oscar.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/oscar/oscar.c Wed Nov 11 03:07:21 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; i<strlen(text[0]); i++) - num = num*10 + text[0][i]-48; - for (i=0; i<num; i++) { - struct name_data *data = g_new(struct name_data, 1); - gchar *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->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; i<num; i++) { + struct name_data *data; + gchar *message; + + if (!text[i*2 + 1] || !text[i*2 + 2]) { + /* We're missing the contact name or nickname. Bail out. */ + gchar *tmp = g_strescape(args->msg, 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;
--- a/libpurple/protocols/oscar/oscar.h Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/oscar/oscar.h Wed Nov 11 03:07:21 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. */
--- a/libpurple/protocols/oscar/oscarcommon.h Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/oscar/oscarcommon.h Wed Nov 11 03:07:21 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);
--- a/libpurple/protocols/silc/Makefile.mingw Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/silc/Makefile.mingw Wed Nov 11 03:07:21 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
--- a/libpurple/protocols/silc10/Makefile.mingw Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/silc10/Makefile.mingw Wed Nov 11 03:07:21 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
--- a/libpurple/protocols/yahoo/libyahoo.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/yahoo/libyahoo.c Wed Nov 11 03:07:21 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 */
--- a/libpurple/protocols/yahoo/libymsg.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/yahoo/libymsg.c Wed Nov 11 03:07:21 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, "<ding>")) { 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); } @@ -3990,7 +3993,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), @@ -4030,10 +4033,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), @@ -4358,17 +4363,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 */ @@ -4500,17 +4495,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 ) @@ -4802,18 +4787,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) @@ -4925,18 +4901,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) @@ -4957,17 +4923,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)
--- a/libpurple/protocols/yahoo/libymsg.h Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/yahoo/libymsg.h Wed Nov 11 03:07:21 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);
--- a/libpurple/protocols/yahoo/util.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/yahoo/util.c Wed Nov 11 03:07:21 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; +} +
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/yahoo/yahoo_filexfer.c Wed Nov 11 03:07:21 2009 +0000 @@ -1067,6 +1067,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;
--- a/libpurple/protocols/yahoo/yahoo_filexfer.h Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/protocols/yahoo/yahoo_filexfer.h Wed Nov 11 03:07:21 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.
--- a/libpurple/server.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/server.c Wed Nov 11 03:07:21 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);
--- a/libpurple/tests/test_jabber_jutil.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/tests/test_jabber_jutil.c Wed Nov 11 03:07:21 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");
--- a/libpurple/theme-loader.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/theme-loader.c Wed Nov 11 03:07:21 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); }
--- a/libpurple/win32/global.mak Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/win32/global.mak Wed Nov 11 03:07:21 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
--- a/libpurple/win32/win32dep.c Mon Nov 02 23:14:33 2009 +0000 +++ b/libpurple/win32/win32dep.c Wed Nov 11 03:07:21 2009 +0000 @@ -30,14 +30,6 @@ #include "notify.h" /* - * DEFINES & MACROS - */ - -/* For shfolder.dll */ -typedef HRESULT (CALLBACK* LPFNSHGETFOLDERPATHA)(HWND, int, HANDLE, DWORD, LPSTR); -typedef HRESULT (CALLBACK* LPFNSHGETFOLDERPATHW)(HWND, int, HANDLE, DWORD, LPWSTR); - -/* * LOCALS */ static char *app_data_dir = NULL, *install_dir = NULL, @@ -115,21 +107,12 @@ /* Get paths to special Windows folders. */ gchar *wpurple_get_special_folder(int folder_type) { - static LPFNSHGETFOLDERPATHW MySHGetFolderPathW = NULL; gchar *retval = NULL; - - if (!MySHGetFolderPathW) { - MySHGetFolderPathW = (LPFNSHGETFOLDERPATHW) - wpurple_find_and_loadproc("shfolder.dll", "SHGetFolderPathW"); - } + wchar_t utf_16_dir[MAX_PATH + 1]; - if (MySHGetFolderPathW) { - wchar_t utf_16_dir[MAX_PATH + 1]; - - if (SUCCEEDED(MySHGetFolderPathW(NULL, folder_type, NULL, - SHGFP_TYPE_CURRENT, utf_16_dir))) { - retval = g_utf16_to_utf8(utf_16_dir, -1, NULL, NULL, NULL); - } + if (SUCCEEDED(SHGetFolderPathW(NULL, folder_type, NULL, + SHGFP_TYPE_CURRENT, utf_16_dir))) { + retval = g_utf16_to_utf8(utf_16_dir, -1, NULL, NULL, NULL); } return retval;
--- a/pidgin.desktop.in Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin.desktop.in Wed Nov 11 03:07:21 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
--- a/pidgin/Makefile.am Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/Makefile.am Wed Nov 11 03:07:21 2009 +0000 @@ -87,6 +87,7 @@ gtkdialogs.c \ gtkdnd-hints.c \ gtkdocklet.c \ + gtkdocklet-gtk.c \ gtkdocklet-x11.c \ gtkeventloop.c \ gtkft.c \
--- a/pidgin/Makefile.mingw Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/Makefile.mingw Wed Nov 11 03:07:21 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,9 +57,9 @@ ## PIDGIN_C_SRC = \ gtkaccount.c \ - gtkblist.c \ + gtkblist-theme-loader.c \ gtkblist-theme.c \ - gtkblist-theme-loader.c \ + gtkblist.c \ gtkcellrendererexpander.c \ gtkcertmgr.c \ gtkconn.c \ @@ -68,8 +70,8 @@ gtkdocklet.c \ gtkeventloop.c \ gtkft.c \ + gtkicon-theme-loader.c \ gtkicon-theme.c \ - gtkicon-theme-loader.c \ gtkidle.c \ gtkimhtml.c \ gtkimhtmltoolbar.c \
--- a/pidgin/gtkblist.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/gtkblist.c Wed Nov 11 03:07:21 2009 +0000 @@ -4108,6 +4108,12 @@ } } + if (hidden_conv) { + char *tmp = nametext; + nametext = g_strdup_printf("<b>%s</b>", tmp); + g_free(tmp); + } + /* Put it all together */ if ((!aliased || biglist) && (statustext || idletime)) { /* using <span size='smaller'> breaks the status, so it must be seperated into <small><span>*/
--- a/pidgin/gtkconn.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/gtkconn.c Wed Nov 11 03:07:21 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)
--- a/pidgin/gtkconv.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/gtkconv.c Wed Nov 11 03:07:21 2009 +0000 @@ -4256,7 +4256,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); } @@ -7534,6 +7534,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) { @@ -8028,6 +8050,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);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkdocklet-gtk.c Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,162 @@ +/* + * System tray icon (aka docklet) plugin for Purple + * + * Copyright (C) 2007 Anders Hasselqvist + * + * 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., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "internal.h" +#include "pidgin.h" +#include "debug.h" +#include "prefs.h" +#include "pidginstock.h" +#include "gtkdocklet.h" + +#if GTK_CHECK_VERSION(2,10,0) + +/* globals */ +GtkStatusIcon *docklet = NULL; + +static void +docklet_gtk_status_activated_cb(GtkStatusIcon *status_icon, gpointer user_data) +{ + purple_debug_info("docklet", "button clicked %d\n", 1); + + pidgin_docklet_clicked(1); +} + +static void +docklet_gtk_status_clicked_cb(GtkStatusIcon *status_icon, guint button, guint activate_time, gpointer user_data) +{ + purple_debug_info("docklet", "button clicked %d\n", button); + + pidgin_docklet_clicked(button); +} + +static void +docklet_gtk_status_update_icon(PurpleStatusPrimitive status, gboolean connecting, gboolean pending) +{ + const gchar *icon_name = NULL; + + switch (status) { + case PURPLE_STATUS_OFFLINE: + icon_name = PIDGIN_STOCK_TRAY_OFFLINE; + break; + case PURPLE_STATUS_AWAY: + icon_name = PIDGIN_STOCK_TRAY_AWAY; + break; + case PURPLE_STATUS_UNAVAILABLE: + icon_name = PIDGIN_STOCK_TRAY_BUSY; + break; + case PURPLE_STATUS_EXTENDED_AWAY: + icon_name = PIDGIN_STOCK_TRAY_XA; + break; + case PURPLE_STATUS_INVISIBLE: + icon_name = PIDGIN_STOCK_TRAY_INVISIBLE; + break; + default: + icon_name = PIDGIN_STOCK_TRAY_AVAILABLE; + break; + } + + if (pending) + icon_name = PIDGIN_STOCK_TRAY_PENDING; + if (connecting) + icon_name = PIDGIN_STOCK_TRAY_CONNECT; + + if (icon_name) { + gtk_status_icon_set_from_stock(docklet, icon_name); + } +} + +static void +docklet_gtk_status_set_tooltip(gchar *tooltip) +{ + if (tooltip) { + gtk_status_icon_set_tooltip(docklet, tooltip); + } else { + gtk_status_icon_set_tooltip(docklet, NULL); + } +} + +static void +docklet_gtk_status_position_menu(GtkMenu *menu, + int *x, int *y, gboolean *push_in, + gpointer user_data) +{ + gtk_status_icon_position_menu(menu, x, y, push_in, docklet); +} + +static void +docklet_gtk_status_destroy(void) +{ + g_return_if_fail(docklet != NULL); + + pidgin_docklet_remove(); + + g_object_unref(G_OBJECT(docklet)); + docklet = NULL; + + purple_debug_info("docklet", "destroyed\n"); +} + +static void +docklet_gtk_status_create(gboolean recreate) +{ + if (docklet) { + /* if this is being called when a tray icon exists, it's because + something messed up. try destroying it before we proceed, + although docklet_refcount may be all hosed. hopefully won't happen. */ + purple_debug_warning("docklet", "trying to create icon but it already exists?\n"); + docklet_gtk_status_destroy(); + } + + docklet = gtk_status_icon_new(); + g_return_if_fail(docklet != NULL); + + g_signal_connect(G_OBJECT(docklet), "activate", G_CALLBACK(docklet_gtk_status_activated_cb), NULL); + g_signal_connect(G_OBJECT(docklet), "popup-menu", G_CALLBACK(docklet_gtk_status_clicked_cb), NULL); + + pidgin_docklet_embedded(); + gtk_status_icon_set_visible(docklet, TRUE); + purple_debug_info("docklet", "created\n"); +} + +static void +docklet_gtk_status_create_ui_op(void) +{ + docklet_gtk_status_create(FALSE); +} + +static struct docklet_ui_ops ui_ops = +{ + docklet_gtk_status_create_ui_op, + docklet_gtk_status_destroy, + docklet_gtk_status_update_icon, + NULL, + docklet_gtk_status_set_tooltip, + docklet_gtk_status_position_menu +}; + +void +docklet_ui_init(void) +{ + pidgin_docklet_set_ui_ops(&ui_ops); +} + +#endif /* GTK_CHECK_VERSION(2,10,0) */ +
--- a/pidgin/gtkdocklet-x11.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/gtkdocklet-x11.c Wed Nov 11 03:07:21 2009 +0000 @@ -34,6 +34,8 @@ #include "gtkdocklet.h" #include <gdk/gdkkeysyms.h> +#if !GTK_CHECK_VERSION(2,10,0) + #define SHORT_EMBED_TIMEOUT 5000 #define LONG_EMBED_TIMEOUT 15000 @@ -352,3 +354,6 @@ purple_prefs_add_none(PIDGIN_PREFS_ROOT "/docklet/x11"); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/docklet/x11/embedded", FALSE); } + +#endif /* !GTK_CHECK_VERSION(2,10,0) */ +
--- a/pidgin/gtkdocklet.h Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/gtkdocklet.h Wed Nov 11 03:07:21 2009 +0000 @@ -49,7 +49,7 @@ void pidgin_docklet_uninit(void); void*pidgin_docklet_get_handle(void); -/* function in gtkdocklet-{x11,win32}.c */ +/* function in gtkdocklet-{gtk,x11,win32}.c */ void docklet_ui_init(void); #endif /* _GTKDOCKLET_H_ */
--- a/pidgin/gtkimhtml.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/gtkimhtml.c Wed Nov 11 03:07:21 2009 +0000 @@ -4929,7 +4929,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 @@ -4951,7 +4951,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); }
--- a/pidgin/gtkmain.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/gtkmain.c Wed Nov 11 03:07:21 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 * @@ -743,7 +803,7 @@ return 1; } - g_set_application_name(_("Pidgin")); + g_set_application_name(PIDGIN_NAME); #ifdef _WIN32 winpidgin_init(hint);
--- a/pidgin/gtkmedia.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/gtkmedia.c Wed Nov 11 03:07:21 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;
--- a/pidgin/gtkpounce.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/gtkpounce.c Wed Nov 11 03:07:21 2009 +0000 @@ -1442,7 +1442,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) ?
--- a/pidgin/gtkprefs.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/gtkprefs.c Wed Nov 11 03:07:21 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); @@ -1878,23 +1880,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 */ @@ -1913,9 +1919,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()) { @@ -1959,9 +1965,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", @@ -1969,21 +1981,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:")); @@ -2004,11 +2012,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", @@ -2023,7 +2031,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);
--- a/pidgin/gtkrequest.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/gtkrequest.c Wed Nov 11 03:07:21 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); @@ -637,6 +668,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; @@ -1142,6 +1175,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);
--- a/pidgin/gtkroomlist.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/gtkroomlist.c Wed Nov 11 03:07:21 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)
--- a/pidgin/gtkstatusbox.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/gtkstatusbox.c Wed Nov 11 03:07:21 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; @@ -1227,7 +1238,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); } } @@ -1236,13 +1247,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 @@ -1890,7 +1902,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, @@ -2291,18 +2303,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) { @@ -2353,6 +2353,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 */ @@ -2361,7 +2362,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]; @@ -2369,37 +2369,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(); @@ -2407,27 +2405,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; + } } } @@ -2470,11 +2472,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) @@ -2564,7 +2566,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; } @@ -2622,14 +2624,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; } }
--- a/pidgin/gtkutils.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/gtkutils.c Wed Nov 11 03:07:21 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) @@ -3395,7 +3395,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; @@ -3403,9 +3403,46 @@ } 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; + wchar_t start[256]; + ret = RegEnumKeyExW(HKEY_CLASSES_ROOT, idx++, start, &nameSize, + NULL, NULL, NULL, NULL); + if (ret == ERROR_SUCCESS) { + HKEY reg_key = NULL; + ret = RegOpenKeyExW(HKEY_CLASSES_ROOT, start, 0, KEY_READ, ®_key); + if (ret == ERROR_SUCCESS) { + ret = RegQueryValueExW(reg_key, L"URL Protocol", NULL, NULL, NULL, NULL); + if (ret == ERROR_SUCCESS) { + gchar *utf8 = g_utf16_to_utf8(start, -1, NULL, NULL, NULL); + gchar *protocol = g_strdup_printf("%s:", utf8); + g_free(utf8); + 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); @@ -3423,6 +3460,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) @@ -3430,16 +3472,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; }
--- a/pidgin/pidginstock.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/pidginstock.c Wed Nov 11 03:07:21 2009 +0000 @@ -198,7 +198,10 @@ { PIDGIN_STOCK_STATUS_LOGOUT, "status", "log-out.png", TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, TRUE, NULL }, { PIDGIN_STOCK_STATUS_OFFLINE, "status", "offline.png", TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, PIDGIN_STOCK_STATUS_OFFLINE_I }, { PIDGIN_STOCK_STATUS_PERSON, "status", "person.png", TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL }, - { PIDGIN_STOCK_STATUS_MESSAGE, "toolbar", "message-new.png", TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL }, + { PIDGIN_STOCK_STATUS_MESSAGE, "toolbar", "message-new.png", TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL } +}; + +const SizedStockIcon sized_tray_icons [] = { { PIDGIN_STOCK_TRAY_AVAILABLE, "tray", "tray-online.png", FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL }, { PIDGIN_STOCK_TRAY_INVISIBLE, "tray", "tray-invisible.png", FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL }, @@ -332,7 +335,8 @@ static void add_sized_icon(GtkIconSet *iconset, GtkIconSize sizeid, PidginIconTheme *theme, - const char *size, SizedStockIcon sized_icon, gboolean translucent) + const char *size, SizedStockIcon sized_icon, gboolean translucent, + gboolean size_wildcarded) { char *filename; GtkIconSource *source; @@ -349,7 +353,7 @@ gtk_icon_source_set_direction(source, GTK_TEXT_DIR_LTR); gtk_icon_source_set_direction_wildcarded(source, !sized_icon.rtl); gtk_icon_source_set_size(source, sizeid); - gtk_icon_source_set_size_wildcarded(source, FALSE); + gtk_icon_source_set_size_wildcarded(source, size_wildcarded); gtk_icon_source_set_state_wildcarded(source, TRUE); gtk_icon_set_add_source(iconset, source); gtk_icon_source_free(source); @@ -359,7 +363,7 @@ gtk_icon_source_set_pixbuf(source, pixbuf); gtk_icon_source_set_direction_wildcarded(source, TRUE); gtk_icon_source_set_size(source, GTK_ICON_SIZE_MENU); - gtk_icon_source_set_size_wildcarded(source, FALSE); + gtk_icon_source_set_size_wildcarded(source, size_wildcarded); gtk_icon_source_set_state_wildcarded(source, TRUE); gtk_icon_set_add_source(iconset, source); gtk_icon_source_free(source); @@ -379,7 +383,7 @@ gtk_icon_source_set_filename(source, filename); gtk_icon_source_set_direction(source, GTK_TEXT_DIR_RTL); gtk_icon_source_set_size(source, sizeid); - gtk_icon_source_set_size_wildcarded(source, FALSE); + gtk_icon_source_set_size_wildcarded(source, size_wildcarded); gtk_icon_source_set_state_wildcarded(source, TRUE); gtk_icon_set_add_source(iconset, source); g_free(filename); @@ -435,9 +439,9 @@ #define ADD_SIZED_ICON(name, size) \ if (sized_status_icons[i].name) { \ - add_sized_icon(normal, name, PIDGIN_ICON_THEME(theme), size, sized_status_icons[i], FALSE); \ + add_sized_icon(normal, name, PIDGIN_ICON_THEME(theme), size, sized_status_icons[i], FALSE, FALSE); \ if (sized_status_icons[i].translucent_name) \ - add_sized_icon(translucent, name, PIDGIN_ICON_THEME(theme), size, sized_status_icons[i], TRUE); \ + add_sized_icon(translucent, name, PIDGIN_ICON_THEME(theme), size, sized_status_icons[i], TRUE, FALSE); \ } ADD_SIZED_ICON(microscopic, "11"); ADD_SIZED_ICON(extra_small, "16"); @@ -456,6 +460,35 @@ } } + for (i = 0; i < G_N_ELEMENTS(sized_tray_icons); i++) + { + normal = gtk_icon_set_new(); + if (sized_tray_icons[i].translucent_name) + translucent = gtk_icon_set_new(); + +#define ADD_SIZED_ICON(name, size) \ + if (sized_tray_icons[i].name) { \ + add_sized_icon(normal, name, PIDGIN_ICON_THEME(theme), size, sized_tray_icons[i], FALSE, TRUE); \ + if (sized_tray_icons[i].translucent_name) \ + add_sized_icon(translucent, name, PIDGIN_ICON_THEME(theme), size, sized_tray_icons[i], TRUE, TRUE); \ + } + ADD_SIZED_ICON(microscopic, "11"); + ADD_SIZED_ICON(extra_small, "16"); + ADD_SIZED_ICON(small, "22"); + ADD_SIZED_ICON(medium, "32"); + ADD_SIZED_ICON(large, "48"); + ADD_SIZED_ICON(huge, "64"); +#undef ADD_SIZED_ICON + + gtk_icon_factory_add(icon_factory, sized_tray_icons[i].name, normal); + gtk_icon_set_unref(normal); + + if (sized_tray_icons[i].translucent_name) { + gtk_icon_factory_add(icon_factory, sized_tray_icons[i].translucent_name, translucent); + gtk_icon_set_unref(translucent); + } + } + gtk_widget_destroy(win); g_object_unref(G_OBJECT(icon_factory)); reload_settings(); @@ -527,7 +560,7 @@ #define ADD_SIZED_ICON(name, size) \ if (sized_stock_icons[i].name) \ - add_sized_icon(iconset, name, PIDGIN_ICON_THEME(theme), size, sized_stock_icons[i], FALSE); + add_sized_icon(iconset, name, PIDGIN_ICON_THEME(theme), size, sized_stock_icons[i], FALSE, FALSE); ADD_SIZED_ICON(microscopic, "11"); ADD_SIZED_ICON(extra_small, "16"); ADD_SIZED_ICON(small, "22");
--- a/pidgin/pidginstock.h Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/pidginstock.h Wed Nov 11 03:07:21 2009 +0000 @@ -161,7 +161,7 @@ #endif /* Tray icons */ -#define PIDGIN_STOCK_TRAY_AVAILABLE "pidgin-tray-available" +#define PIDGIN_STOCK_TRAY_AVAILABLE "pidgin-tray-online" #define PIDGIN_STOCK_TRAY_INVISIBLE "pidgin-tray-invisible" #define PIDGIN_STOCK_TRAY_AWAY "pidgin-tray-away" #define PIDGIN_STOCK_TRAY_BUSY "pidgin-tray-busy"
--- a/pidgin/pixmaps/Makefile.am Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/pixmaps/Makefile.am Wed Nov 11 03:07:21 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 \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/pixmaps/protocols/scalable/mxit.svg Wed Nov 11 03:07:21 2009 +0000 @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve"> +<image overflow="visible" width="34" height="39" xlink:href="data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/7AARRHVja3kAAQAEAAAAHgAA/+4AIUFkb2JlAGTAAAAAAQMA +EAMCAwYAAAH0AAACSgAAA6n/2wCEABALCwsMCxAMDBAXDw0PFxsUEBAUGx8XFxcXFx8eFxoaGhoX +Hh4jJSclIx4vLzMzLy9AQEBAQEBAQEBAQEBAQEABEQ8PERMRFRISFRQRFBEUGhQWFhQaJhoaHBoa +JjAjHh4eHiMwKy4nJycuKzU1MDA1NUBAP0BAQEBAQEBAQEBAQP/CABEIACgAIwMBIgACEQEDEQH/ +xACyAAEAAwEBAAAAAAAAAAAAAAAAAQMFBAIBAAMBAQAAAAAAAAAAAAAAAAABAgMEEAABBAECBgMB +AAAAAAAAAAABAAIDBAUREyESIhQVRRAjNQYRAAEDAgMDBwgLAAAAAAAAAAERAgMAEiETBDFRIkFh +cUIUhcWhMlKDs9M01GKSIzNzwyRkhJQVEgABAwAGCQUAAAAAAAAAAAAAARECIUFRYYESEDFxodEy +QoKicjNDNIT/2gAMAwEAAhEDEQAAANWbtDvwx51+eStam/NlFqUEyBo7eck8dAFoyr//2gAIAQIA +AQUAe+RpEry7QJ4POA4FDeR3V9q//9oACAEDAAEFAAAUWjRDTTh8dC6V0r//2gAIAQEAAQUAiihn +hNOsu1rBMr0HLks9hi7UdSE5ytyx5evM/J6dx6rCcInfp3ON2+CZfVY1z46RMm9IHyveZpW+q8Vc +rtkr51qbF/QEx0s04eCg2//aAAgBAgIGPwBoxWSbRIyisXvNSfZsJrbFYkFblRlP1FOTHK+J8fid +XuefE//aAAgBAwIGPwCmTDpJzsE9Tkrzs0VlXLuP/9oACAEBAQY/AIZ54GanU6mJk8sksTZnEyNa +9Be11rW3IAMK+ChH8SP3dY6KH+pH7uvhNORy2wxscOhzGtc01n9rmvzP85bup2zs2b+JZ1q0csgJ +adHC3hRVMcR5SN1XWSIqLw7fr0GNa8KQ242oC7Aea4moCAFc14cd4FqV3n4lWjP7KL2cNXfT/Kph +3ZPtDUL0Nrbmk7i61K7z8SrQyNAd+khaQSm2OPmO6s5G3XXWrgllm1KLyjCjbUN2LSXLsFBha0BW +kkOJ80g7Leau8vEqy9HPEdO0/ZRTxOe6NvoNeyWPhHIow31wt0rvVS/MVjHph6qX5iuKXTQr1mwv +Lhzi+dwXpFZWdNZk5SX9fMzs/Z95fivkr//Z" transform="matrix(0.9999 0 0 0.9999 7.0146 6.0142)"> +</image> +</svg>
--- a/pidgin/plugins/disco/gtkdisco.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/plugins/disco/gtkdisco.c Wed Nov 11 03:07:21 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;
--- a/pidgin/plugins/disco/gtkdisco.h Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/plugins/disco/gtkdisco.h Wed Nov 11 03:07:21 2009 +0000 @@ -43,6 +43,8 @@ PurpleAccount *account; PidginDiscoList *discolist; + + gpointer *prompt_handle; }; struct _PidginDiscoList {
--- a/pidgin/plugins/perl/common/Makefile.mingw Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/plugins/perl/common/Makefile.mingw Wed Nov 11 03:07:21 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
--- a/pidgin/plugins/win32/winprefs/Makefile.mingw Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/plugins/win32/winprefs/Makefile.mingw Wed Nov 11 03:07:21 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 ##
--- a/pidgin/win32/gtkwin32dep.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/gtkwin32dep.c Wed Nov 11 03:07:21 2009 +0000 @@ -383,6 +383,7 @@ } void winpidgin_init(HINSTANCE hint) { + FARPROC exchndl_SetLogFile; purple_debug_info("winpidgin", "winpidgin_init start\n"); @@ -400,6 +401,16 @@ MyFlashWindowEx = (LPFNFLASHWINDOWEX) wpurple_find_and_loadproc("user32.dll", "FlashWindowEx"); + exchndl_SetLogFile = wpurple_find_and_loadproc("exchndl.dll", "SetLogFile"); + if (exchndl_SetLogFile) { + gchar *filename = g_build_filename(purple_user_dir(), + "pidgin.RPT", NULL); + purple_debug_info("winpidgin", "Setting exchndl.dll LogFile to %s\n", + filename); + (exchndl_SetLogFile)(filename); + g_free(filename); + } + purple_debug_info("winpidgin", "winpidgin_init end\n"); }
--- a/pidgin/win32/nsis/langmacros.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/langmacros.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -31,7 +31,6 @@ ; Startup checks !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT INSTALLER_IS_RUNNING ${CUR_LANG} !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT PIDGIN_IS_RUNNING ${CUR_LANG} - !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT GTK_INSTALLER_NEEDED ${CUR_LANG} ; License Page !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT PIDGIN_LICENSE_BUTTON ${CUR_LANG} @@ -48,9 +47,7 @@ !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT PIDGIN_SHORTCUTS_SECTION_DESCRIPTION ${CUR_LANG} !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT PIDGIN_DESKTOP_SHORTCUT_DESC ${CUR_LANG} !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT PIDGIN_STARTMENU_SHORTCUT_DESC ${CUR_LANG} - - ; GTK+ Directory Page - !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT GTK_UPGRADE_PROMPT ${CUR_LANG} + !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT DEBUG_SYMBOLS_SECTION_TITLE ${CUR_LANG} ; Installer Finish Page !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT PIDGIN_FINISH_VISIT_WEB_SITE ${CUR_LANG} @@ -58,10 +55,6 @@ ; Pidgin Section Prompts and Texts !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL ${CUR_LANG} - ; GTK+ Section Prompts - !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT GTK_INSTALL_ERROR ${CUR_LANG} - !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT GTK_BAD_INSTALL_PATH ${CUR_LANG} - ; URI Handler section !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT URI_HANDLERS_SECTION_TITLE ${CUR_LANG} @@ -98,6 +91,9 @@ !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT PIDGIN_SPELLCHECK_SWEDISH ${CUR_LANG} !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT PIDGIN_SPELLCHECK_UKRAINIAN ${CUR_LANG} + !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT PIDGIN_DEBUGSYMBOLS_ERROR ${CUR_LANG} + !insertmacro PIDGIN_MACRO_LANGSTRING_INSERT PIDGIN_GTK_DOWNLOAD_ERROR ${CUR_LANG} + !undef CUR_LANG !macroend
--- a/pidgin/win32/nsis/pidgin-installer.nsi Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/pidgin-installer.nsi Wed Nov 11 03:07:21 2009 +0000 @@ -8,7 +8,6 @@ ;-------------------------------- ;Global Variables Var name -Var GTK_FOLDER Var ISSILENT Var STARTUP_RUN_KEY Var SPELLCHECK_SEL @@ -19,14 +18,10 @@ ;The name var is set in .onInit Name $name -!ifdef WITH_GTK -OutFile "pidgin-${PIDGIN_VERSION}.exe" +!ifdef OFFLINE_INSTALLER +OutFile "pidgin-${PIDGIN_VERSION}-offline.exe" !else -!ifdef DEBUG -OutFile "pidgin-${PIDGIN_VERSION}-debug.exe" -!else -OutFile "pidgin-${PIDGIN_VERSION}-no-gtk.exe" -!endif +OutFile "pidgin-${PIDGIN_VERSION}.exe" !endif SetCompressor /SOLID lzma @@ -40,6 +35,7 @@ !include "Sections.nsh" !include "WinVer.nsh" !include "LogicLib.nsh" +!include "Memento.nsh" !include "FileFunc.nsh" !insertmacro GetParameters @@ -51,11 +47,13 @@ !insertmacro WordFind !insertmacro un.WordFind +!include "TextFunc.nsh" + ;-------------------------------- ;Defines !define PIDGIN_NSIS_INCLUDE_PATH "." -!define PIDGIN_INSTALLER_DEPS "..\..\..\..\win32-dev\pidgin-inst-deps" +!define PIDGIN_INSTALLER_DEPS "..\..\..\..\win32-dev\pidgin-inst-deps-0.2" ; Remove these and the stuff that uses them at some point !define OLD_GAIM_REG_KEY "SOFTWARE\gaim" @@ -70,15 +68,15 @@ !define PIDGIN_UNINST_EXE "pidgin-uninst.exe" !define GTK_MIN_VERSION "2.14.0" -!define GTK_REG_KEY "SOFTWARE\GTK\2.0" !define PERL_REG_KEY "SOFTWARE\Perl" !define PERL_DLL "perl510.dll" -!define GTK_DEFAULT_INSTALL_PATH "$COMMONFILES\GTK\2.0" -!define GTK_RUNTIME_INSTALLER "..\..\..\..\gtk_installer\gtk-runtime-${GTK_INSTALL_VERSION}*.exe" !define ASPELL_REG_KEY "SOFTWARE\Aspell" !define DOWNLOADER_URL "http://pidgin.im/win32/download_redir.php" +!define MEMENTO_REGISTRY_ROOT HKLM +!define MEMENTO_REGISTRY_KEY "${PIDGIN_UNINSTALL_KEY}" + ;-------------------------------- ;Version resource VIProductVersion "${PIDGIN_PRODUCT_VERSION}" @@ -86,14 +84,10 @@ VIAddVersionKey "FileVersion" "${PIDGIN_VERSION}" VIAddVersionKey "ProductVersion" "${PIDGIN_VERSION}" VIAddVersionKey "LegalCopyright" "" -!ifdef WITH_GTK -VIAddVersionKey "FileDescription" "Pidgin Installer (w/ GTK+ Installer)" +!ifdef OFFLINE_INSTALLER +VIAddVersionKey "FileDescription" "Pidgin Installer (Offline)" !else -!ifdef DEBUG -VIAddVersionKey "FileDescription" "Pidgin Installer (Debug Version)" -!else -VIAddVersionKey "FileDescription" "Pidgin Installer (w/o GTK+ Installer)" -!endif +VIAddVersionKey "FileDescription" "Pidgin Installer" !endif ;-------------------------------- @@ -138,14 +132,6 @@ !insertmacro MUI_PAGE_LICENSE "../../../COPYING" !insertmacro MUI_PAGE_COMPONENTS -!ifdef WITH_GTK - ; GTK+ install dir page - !define MUI_PAGE_CUSTOMFUNCTION_PRE preGtkDirPage - !define MUI_PAGE_CUSTOMFUNCTION_LEAVE postGtkDirPage - !define MUI_DIRECTORYPAGE_VARIABLE $GTK_FOLDER - !insertmacro MUI_PAGE_DIRECTORY -!endif - ; Pidgin install dir page !insertmacro MUI_PAGE_DIRECTORY @@ -314,7 +300,7 @@ IfErrors uninstall_problem ; Ready to uninstall.. ClearErrors - ExecWait '"$TEMP\$R6" /S _?=$R1' + ExecWait '"$TEMP\$R6" /S /KEEPGTK=1 _?=$R1' IfErrors exec_error Delete "$TEMP\$R6" Goto done @@ -345,77 +331,38 @@ ;-------------------------------- ;GTK+ Runtime Install Section -!ifdef WITH_GTK Section $(GTK_SECTION_TITLE) SecGtk - Call CheckUserInstallRights - Pop $R1 - - SetOutPath $TEMP - SetOverwrite on - File /oname=gtk-runtime.exe ${GTK_RUNTIME_INSTALLER} - SetOverwrite off - - Call DoWeNeedGtk - Pop $R0 - Pop $R6 + InitPluginsDir + StrCpy $R1 "$PLUGINSDIR\gtk.zip" +!ifdef OFFLINE_INSTALLER - StrCmp $R0 "0" have_gtk - StrCmp $R0 "1" upgrade_gtk - StrCmp $R0 "2" upgrade_gtk - ;StrCmp $R0 "3" no_gtk no_gtk + SetOutPath $PLUGINSDIR + File /oname=gtk.zip "..\..\..\..\gtk_installer\gtk-runtime-${GTK_INSTALL_VERSION}.zip" - ;no_gtk: - StrCmp $R1 "NONE" gtk_no_install_rights - ClearErrors - ExecWait '"$TEMP\gtk-runtime.exe" /L=$LANGUAGE $ISSILENT /D=$GTK_FOLDER' - IfErrors gtk_install_error done - - upgrade_gtk: - StrCpy $GTK_FOLDER $R6 - StrCmp $R0 "2" +2 ; Upgrade isn't optional - MessageBox MB_YESNO $(GTK_UPGRADE_PROMPT) /SD IDYES IDNO done - ClearErrors - ExecWait '"$TEMP\gtk-runtime.exe" /L=$LANGUAGE $ISSILENT /D=$GTK_FOLDER' - IfErrors gtk_install_error done +!else - gtk_install_error: - Delete "$TEMP\gtk-runtime.exe" - MessageBox MB_OK $(GTK_INSTALL_ERROR) /SD IDOK - Quit - - have_gtk: - StrCpy $GTK_FOLDER $R6 - StrCmp $R1 "NONE" done ; If we have no rights, we can't re-install - ; Even if we have a sufficient version of GTK+, we give user choice to re-install. - ClearErrors - ExecWait '"$TEMP\gtk-runtime.exe" /L=$LANGUAGE $ISSILENT' - IfErrors gtk_install_error - Goto done + ; We need to download the GTK+ runtime + retry: + StrCpy $R2 "${DOWNLOADER_URL}?version=${PIDGIN_VERSION}>k_version=${GTK_INSTALL_VERSION}&dl_pkg=gtk" + DetailPrint "Downloading GTK+ Runtime ... ($R2)" + NSISdl::download /TIMEOUT=10000 $R2 $R1 + Pop $R0 + StrCmp $R0 "cancel" done + StrCmp $R0 "success" +2 + MessageBox MB_RETRYCANCEL "$(PIDGIN_GTK_DOWNLOAD_ERROR) : $R1" /SD IDCANCEL IDRETRY retry IDCANCEL done - ;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ; end got_install rights +!endif + + SetOutPath "$INSTDIR" - gtk_no_install_rights: - ; Install GTK+ to Pidgin install dir - StrCpy $GTK_FOLDER $INSTDIR - ClearErrors - ExecWait '"$TEMP\gtk-runtime.exe" /L=$LANGUAGE $ISSILENT /D=$GTK_FOLDER' - IfErrors gtk_install_error - SetOverwrite on - ClearErrors - CopyFiles /FILESONLY "$GTK_FOLDER\bin\*.dll" $GTK_FOLDER - SetOverwrite off - IfErrors gtk_install_error - Delete "$GTK_FOLDER\bin\*.dll" - Goto done - ;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ; end gtk_no_install_rights + nsisunz::UnzipToLog $R1 "$INSTDIR" + Pop $R0 + StrCmp $R0 "success" +2 + DetailPrint "$R0" ;print error message to log done: - Delete "$TEMP\gtk-runtime.exe" SectionEnd ; end of GTK+ section -!endif ;-------------------------------- ;Pidgin Install Section @@ -427,15 +374,12 @@ Call CheckUserInstallRights Pop $R0 - ; Get GTK+ lib dir if we have it.. - - StrCmp $R0 "NONE" pidgin_none + StrCmp $R0 "NONE" pidgin_install_files StrCmp $R0 "HKLM" pidgin_hklm pidgin_hkcu pidgin_hklm: - ReadRegStr $R1 HKLM ${GTK_REG_KEY} "Path" WriteRegStr HKLM "${HKLM_APP_PATHS_KEY}" "" "$INSTDIR\pidgin.exe" - WriteRegStr HKLM "${HKLM_APP_PATHS_KEY}" "Path" "$R1\bin" + WriteRegStr HKLM "${HKLM_APP_PATHS_KEY}" "Path" "$INSTDIR\Gtk\bin" WriteRegStr HKLM ${PIDGIN_REG_KEY} "" "$INSTDIR" WriteRegStr HKLM ${PIDGIN_REG_KEY} "Version" "${PIDGIN_VERSION}" WriteRegStr HKLM "${PIDGIN_UNINSTALL_KEY}" "DisplayName" "Pidgin" @@ -449,10 +393,6 @@ Goto pidgin_install_files pidgin_hkcu: - ReadRegStr $R1 HKCU ${GTK_REG_KEY} "Path" - StrCmp $R1 "" 0 +2 - ReadRegStr $R1 HKLM ${GTK_REG_KEY} "Path" - WriteRegStr HKCU ${PIDGIN_REG_KEY} "" "$INSTDIR" WriteRegStr HKCU ${PIDGIN_REG_KEY} "Version" "${PIDGIN_VERSION}" WriteRegStr HKCU "${PIDGIN_UNINSTALL_KEY}" "DisplayName" "Pidgin" @@ -463,9 +403,6 @@ WriteRegStr HKCU "${PIDGIN_UNINSTALL_KEY}" "UninstallString" "$INSTDIR\${PIDGIN_UNINST_EXE}" Goto pidgin_install_files - pidgin_none: - ReadRegStr $R1 HKLM ${GTK_REG_KEY} "Path" - pidgin_install_files: SetOutPath "$INSTDIR" ; Pidgin files @@ -475,18 +412,8 @@ Delete "$INSTDIR\plugins\liboscar.dll" Delete "$INSTDIR\plugins\libjabber.dll" - File /r ..\..\..\${PIDGIN_INSTALL_DIR}\*.* - !ifdef DEBUG + File /r /x locale ..\..\..\${PIDGIN_INSTALL_DIR}\*.* File "${PIDGIN_INSTALLER_DEPS}\exchndl.dll" - !endif - - ; Install shfolder.dll if need be.. - SearchPath $R4 "shfolder.dll" - StrCmp $R4 "" 0 got_shfolder - SetOutPath "$SYSDIR" - File "${PIDGIN_INSTALLER_DEPS}\shfolder.dll" - SetOutPath "$INSTDIR" - got_shfolder: ; Check if Perl is installed, if so add it to the AppPaths ReadRegStr $R2 HKLM ${PERL_REG_KEY} "" @@ -571,6 +498,22 @@ SectionGroupEnd ;-------------------------------- +;Translations + +!macro LANG_SECTION lang + ${MementoUnselectedSection} "${lang}" SecLang_${lang} + SetOutPath "$INSTDIR\locale\${lang}\LC_MESSAGES" + File /oname=pidgin.mo "..\..\..\${PIDGIN_INSTALL_DIR}\locale\${lang}\LC_MESSAGES\pidgin.mo" + SetOutPath "$INSTDIR" + ${MementoSectionEnd} +!macroend +SectionGroup $(TRANSLATIONS_SECTION_TITLE) SecTranslations + # pidgin-translations is generated based on the contents of the locale directory + !include "pidgin-translations.nsh" +SectionGroupEnd +${MementoSectionDone} + +;-------------------------------- ;Spell Checking SectionGroup /e $(PIDGIN_SPELLCHECK_SECTION_TITLE) SecSpellCheck @@ -664,6 +607,37 @@ SectionEnd SectionGroupEnd +Section /o $(DEBUG_SYMBOLS_SECTION_TITLE) SecDebugSymbols + InitPluginsDir + + ; We need to download and extract the debug symbols + StrCpy $R1 "$PLUGINSDIR\pidgin-${PIDGIN_VERSION}-dbgsym.zip" +!ifdef OFFLINE_INSTALLER + + SetOutPath $PLUGINSDIR + File /oname=pidgin-${PIDGIN_VERSION}-dbgsym.zip "..\..\..\..\gtk_installer\gtk-runtime-${GTK_INSTALL_VERSION}.zip" + +!else + + retry: + StrCpy $R2 "${DOWNLOADER_URL}?version=${PIDGIN_VERSION}&dl_pkg=dbgsym" + DetailPrint "Downloading Debug Symbols... ($R2)" + NSISdl::download /TIMEOUT=10000 $R2 $R1 + Pop $R0 + StrCmp $R0 "cancel" done + StrCmp $R0 "success" +2 + MessageBox MB_RETRYCANCEL "$(PIDGIN_DEBUGSYMBOLS_ERROR) : $R2" /SD IDCANCEL IDRETRY retry IDCANCEL done + +!endif + + nsisunz::UnzipToLog $R1 "$INSTDIR" + Pop $R0 + StrCmp $R0 "success" +2 + DetailPrint "$R0" ;print error message to log + + done: +SectionEnd + ;-------------------------------- ;Uninstaller Section @@ -815,11 +789,20 @@ Delete "$INSTDIR\softokn3.dll" Delete "$INSTDIR\ssl3.dll" Delete "$INSTDIR\${PIDGIN_UNINST_EXE}" - !ifdef DEBUG Delete "$INSTDIR\exchndl.dll" - !endif Delete "$INSTDIR\install.log" + ; Remove the debug symbols + RMDir /r "$INSTDIR\pidgin-${PIDGIN_VERSION}-dbgsym" + + ; Remove the local GTK+ copy (if we're not just upgrading) + ${GetParameters} $R0 + ClearErrors + ${GetOptions} "$R3" "/KEEPGTK=" $R1 + IfErrors +2 + StrCmp $R1 "1" +2 + RMDir /r "$INSTDIR\Gtk" + ;Try to remove Pidgin install dir (only if empty) RMDir "$INSTDIR" @@ -845,10 +828,8 @@ !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN !insertmacro MUI_DESCRIPTION_TEXT ${SecPidgin} \ $(PIDGIN_SECTION_DESCRIPTION) -!ifdef WITH_GTK !insertmacro MUI_DESCRIPTION_TEXT ${SecGtk} \ $(GTK_SECTION_DESCRIPTION) -!endif !insertmacro MUI_DESCRIPTION_TEXT ${SecShortcuts} \ $(PIDGIN_SHORTCUTS_SECTION_DESCRIPTION) @@ -1141,93 +1122,42 @@ ; Call DoWeNeedGtk ; First Pop: ; 0 - We have the correct version -; Second Pop: Key where Version was found ; 1 - We have an old version that should work, prompt user for optional upgrade -; Second Pop: HKLM or HKCU depending on where GTK was found. ; 2 - We have an old version that needs to be upgraded -; Second Pop: HKLM or HKCU depending on where GTK was found. ; 3 - We don't have Gtk+ at all -; Second Pop: "NONE, HKLM or HKCU" depending on our rights.. ; Function DoWeNeedGtk - ; Logic should be: - ; - Check what user rights we have (HKLM or HKCU) - ; - If HKLM rights.. - ; - Only check HKLM key for GTK+ - ; - If installed to HKLM, check it and return. - ; - If HKCU rights.. - ; - First check HKCU key for GTK+ - ; - if good or bad exists stop and ret. - ; - If no hkcu gtk+ install, check HKLM - ; - If HKLM ver exists but old, return as if no ver exits. - ; - If no rights - ; - Check HKLM Push $0 Push $1 - Push $2 - Push $3 - Call CheckUserInstallRights - Pop $1 - StrCmp $1 "HKLM" check_hklm - StrCmp $1 "HKCU" check_hkcu check_hklm - check_hkcu: - ReadRegStr $0 HKCU ${GTK_REG_KEY} "Version" - StrCpy $2 "HKCU" - StrCmp $0 "" check_hklm have_gtk + IfFileExists "$INSTDIR\Gtk\CONTENTS" +3 + Push "3" + Goto done - check_hklm: - ReadRegStr $0 HKLM ${GTK_REG_KEY} "Version" - StrCpy $2 "HKLM" - StrCmp $0 "" no_gtk have_gtk - - have_gtk: - ; GTK+ is already installed; check version. - ; Change this to not even run the GTK installer if this version is already installed. - ${VersionCompare} ${GTK_INSTALL_VERSION} $0 $3 - IntCmp $3 1 +1 good_version good_version - ${VersionCompare} ${GTK_MIN_VERSION} $0 $3 + ClearErrors + ${ConfigRead} "$INSTDIR\Gtk\CONTENTS" "Bundle Version " $0 + IfErrors 0 +3 + Push "3" + Goto done - ; Bad version. If hklm ver and we have hkcu or no rights.. return no gtk - StrCmp $1 "NONE" no_gtk ; if no rights.. can't upgrade - StrCmp $1 "HKCU" 0 +2 ; if HKLM can upgrade.. - StrCmp $2 "HKLM" no_gtk ; have hkcu rights.. if found hklm ver can't upgrade.. - Push $2 - IntCmp $3 1 +3 - Push "1" ; Optional Upgrade - Goto done - Push "2" ; Mandatory Upgrade - Goto done + ${VersionCompare} ${GTK_INSTALL_VERSION} $0 $1 + IntCmp $1 1 +3 + Push "0" ; Have a good version + Goto done - good_version: - StrCmp $2 "HKLM" have_hklm_gtk have_hkcu_gtk - have_hkcu_gtk: - ; Have HKCU version - ReadRegStr $0 HKCU ${GTK_REG_KEY} "Path" - Goto good_version_cont - - have_hklm_gtk: - ReadRegStr $0 HKLM ${GTK_REG_KEY} "Path" - Goto good_version_cont - - good_version_cont: - Push $0 ; The path to existing GTK+ - Push "0" - Goto done - - no_gtk: - Push $1 ; our rights - Push "3" - Goto done + ${VersionCompare} ${GTK_MIN_VERSION} $0 $1 + IntCmp $1 1 +3 + Push "1" ; Optional Upgrade + Goto done + Push "2" ; Mandatory Upgrade + Goto done done: - ; The top two items on the stack are what we want to return - Exch 4 + ; The item on the stack is what we want to return + Exch Pop $1 - Exch 4 + Exch Pop $0 - Pop $3 - Pop $2 FunctionEnd @@ -1298,6 +1228,8 @@ DeleteRegValue HKCU "${OLD_GAIM_REG_KEY}" "Installer Language" WriteRegStr HKCU "${PIDGIN_REG_KEY}" "Installer Language" "$R0" + ${MementoSectionRestore} + !insertmacro SetSectionFlag ${SecSpellCheck} ${SF_RO} !insertmacro UnselectSection ${SecSpellCheck} @@ -1344,7 +1276,7 @@ ClearErrors ${GetOptions} "$R3" "/L=" $R1 - IfErrors +4 + IfErrors +3 StrCpy $LANGUAGE $R1 Goto skip_lang @@ -1409,7 +1341,15 @@ Pop $R0 FunctionEnd +Function .onInstSuccess + + ${MementoSectionSave} + +FunctionEnd + + Function un.onInit + Call un.RunCheck StrCpy $name "Pidgin ${PIDGIN_VERSION}" ;LogSet on @@ -1423,98 +1363,23 @@ Function preWelcomePage Push $R0 - -!ifndef WITH_GTK - ; If this installer dosn't have GTK, check whether we need it. - ; We do this here and not in .onInit because language change in - ; .onInit doesn't take effect until it is finished. - Call DoWeNeedGtk - Pop $R0 - Pop $GTK_FOLDER - - IntCmp $R0 1 done done - MessageBox MB_OK $(GTK_INSTALLER_NEEDED) /SD IDOK - Quit - - done: - -!else Push $R1 - Push $R2 Call DoWeNeedGtk Pop $R0 - Pop $R2 - IntCmp $R0 1 gtk_selection_done gtk_not_mandatory + IntCmp $R0 1 done gtk_not_mandatory ; Make the GTK+ Section RO if it is required. !insertmacro SetSectionFlag ${SecGtk} ${SF_RO} - Goto gtk_selection_done + Goto done gtk_not_mandatory: ; Don't select the GTK+ section if we already have this version or newer installed !insertmacro UnselectSection ${SecGtk} - gtk_selection_done: done: - Pop $R2 - Pop $R1 -!endif - Pop $R0 -FunctionEnd - -!ifdef WITH_GTK -Function preGtkDirPage - Push $R0 - Push $R1 - Call DoWeNeedGtk - Pop $R0 - Pop $R1 - - IntCmp $R0 2 +2 +2 no_gtk - StrCmp $R0 "3" no_gtk no_gtk - - ; Don't show dir selector.. Upgrades are done to existing path.. Pop $R1 Pop $R0 - Abort - - no_gtk: - StrCmp $R1 "NONE" 0 no_gtk_cont - ; Got no install rights.. - Pop $R1 - Pop $R0 - Abort - no_gtk_cont: - ; Suggest path.. - StrCmp $R1 "HKCU" 0 hklm1 - ${GetParent} $SMPROGRAMS $R0 - ${GetParent} $R0 $R0 - StrCpy $R0 "$R0\GTK\2.0" - Goto got_path - hklm1: - StrCpy $R0 "${GTK_DEFAULT_INSTALL_PATH}" - - got_path: - StrCpy $name "GTK+ ${GTK_INSTALL_VERSION}" - StrCpy $GTK_FOLDER $R0 - Pop $R1 - Pop $R0 FunctionEnd -Function postGtkDirPage - Push $R0 - StrCpy $name "Pidgin ${PIDGIN_VERSION}" - Push $GTK_FOLDER - Call VerifyDir - Pop $R0 - StrCmp $R0 "0" 0 done - MessageBox MB_OK $(GTK_BAD_INSTALL_PATH) /SD IDOK - Pop $R0 - Abort - done: - Pop $R0 -FunctionEnd -!endif - ; SpellChecker Related Functions ;------------------------------- @@ -1642,6 +1507,8 @@ Pop $R0 ;This is the language code Push $R1 + InitPluginsDir + IfErrors done ; We weren't able to convert the section to lang code retry: @@ -1676,12 +1543,12 @@ IntCmp $R0 15 installed ; If this is the check after installation, don't infinite loop on failure - StrCmp $R1 "$TEMP\aspell_installer.exe" 0 +3 + StrCmp $R1 "$PLUGINSDIR\aspell_installer.exe" 0 +3 StrCpy $R0 $(ASPELL_INSTALL_FAILED) Goto done ; We need to download and install aspell - StrCpy $R1 "$TEMP\aspell_installer.exe" + StrCpy $R1 "$PLUGINSDIR\aspell_installer.exe" StrCpy $R2 "${DOWNLOADER_URL}?version=${PIDGIN_VERSION}&dl_pkg=aspell_core" DetailPrint "Downloading Aspell... ($R2)" NSISdl::download /TIMEOUT=10000 $R2 $R1 @@ -1717,12 +1584,12 @@ StrCmp $R2 "" 0 installed ; If this is the check after installation, don't infinite loop on failure - StrCmp $R1 "$TEMP\aspell_dict-$R0.exe" 0 +3 + StrCmp $R1 "$PLUGINSDIR\aspell_dict-$R0.exe" 0 +3 StrCpy $R0 $(ASPELL_INSTALL_FAILED) Goto done ; We need to download and install aspell - StrCpy $R1 "$TEMP\aspell_dict-$R0.exe" + StrCpy $R1 "$PLUGINSDIR\aspell_dict-$R0.exe" StrCpy $R3 "${DOWNLOADER_URL}?version=${PIDGIN_VERSION}&dl_pkg=lang_$R0" DetailPrint "Downloading the Aspell $R0 Dictionary... ($R3)" NSISdl::download /TIMEOUT=10000 $R3 $R1 @@ -1732,10 +1599,10 @@ Goto done ; Use a specific temporary $OUTDIR for each dictionary because the installer doesn't clean up after itself StrCpy $R4 "$OUTDIR" - SetOutPath "$TEMP\aspell_dict-$R0" + SetOutPath "$PLUGINSDIR\aspell_dict-$R0" ExecWait '"$R1"' SetOutPath "$R4" - RMDir /r "$TEMP\aspell_dict-$R0" + RMDir /r "$PLUGINSDIR\aspell_dict-$R0" Delete $R1 Goto check ; Check that it is now installed correctly
--- a/pidgin/win32/nsis/translations/afrikaans.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/afrikaans.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -12,7 +12,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING "Die installeerder loop reeds." !define PIDGIN_IS_RUNNING "Pidgin loop reeds rens. Verlaat Pidgin eers en probeer dan weer." -!define GTK_INSTALLER_NEEDED "Die GTK+-looptydomgewing is f soek f moet opgegradeer word.$\rInstalleer asb. v{GTK_MIN_VERSION} of hor van die GTK+-looptyd" ; License Page !define PIDGIN_LICENSE_BUTTON "Volgende >" @@ -31,7 +30,6 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "Skep 'n Begin-kieslysinskrywing vir Pidgin" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "'n Ou weergawe van die GTK+-looptyd is gevind. Wil u opgradeer?$\rLet wel: $(^Name) werk dalk net as u so maak." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Besoek die WinPidgin-webblad" @@ -40,8 +38,6 @@ !define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Kan nie die tans genstalleerde weergawe van Pidgin verwyder nie. Die nuwe weergawe sal genstalleer word sonder om die huidige een te verwyder." ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Fout met installering van GTK+-looptyd." -!define GTK_BAD_INSTALL_PATH "Die pad wat u verskaf het, is ontoeganklik of kan nie geskep word nie." ; URL Handler section !define URI_HANDLERS_SECTION_TITLE "URI-hanteerders"
--- a/pidgin/win32/nsis/translations/albanian.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/albanian.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -8,8 +8,6 @@ ;; Author: Besnik Bleta <besnik@spymac.com> ;; -; Startup GTK+ check -!define GTK_INSTALLER_NEEDED "Ose mungon mjedisi GTK+ runtime ose lyp përditësim.$\rJu lutem instaloni GTK+ runtime v${GTK_MIN_VERSION} ose më të vonshëm" ; License Page !define PIDGIN_LICENSE_BUTTON "Më tej >" @@ -23,14 +21,11 @@ ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "U gjet një version i vjetër për GTK+ runtime. Doni të përditësohet?$\rShënim: Pidgin-i mund të mos punojë nëse nuk e bëni." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Vizitoni Faqen Web të Pidgin-it për Windows" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "gabim gjatë instalimit të GTK+ runtime." -!define GTK_BAD_INSTALL_PATH "Shtegu që treguat nuk mund të arrihet ose krijohet." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "Çinstaluesi nuk gjeti dot zëra regjistri për Pidgin-in.$\rKa mundësi që këtë zbatim ta ketë instaluar një tjetër përdorues."
--- a/pidgin/win32/nsis/translations/arabic.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/arabic.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -9,7 +9,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING " ." !define PIDGIN_IS_RUNNING " . ." -!define GTK_INSTALLER_NEEDED " + (GTK+) .$\r v${GTK_MIN_VERSION} +" ; License Page !define PIDGIN_LICENSE_BUTTON " >" @@ -29,7 +28,6 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC " " ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT " +. ˿$\r: $(^Name) ." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE " " @@ -38,8 +36,6 @@ !define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL " . ." ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR " +." -!define GTK_BAD_INSTALL_PATH " ." ; URL Handler section !define URI_HANDLERS_SECTION_TITLE " "
--- a/pidgin/win32/nsis/translations/basque.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/basque.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -1,79 +1,75 @@ -;; -;; basque.nsh -;;Abio-menua - Istanteko Mezularitza -;; Basque language strings for the Windows Pidgin NSIS installer. -;; Windows Code page: 1252 -;; -;; Author: Mikel Pascual Aldabaldetreku <mikel.paskual@gmail.com>, 2007. - -; Startup Checks -!define INSTALLER_IS_RUNNING "Instalatzailea martxan dago." -!define PIDGIN_IS_RUNNING "Pidgin istantzia bat dago martxan. Pidgin itxi eta berriro saiatu." -!define GTK_INSTALLER_NEEDED "GTK+ exekuzio-ingurunea falta da, edo eguneratu egin beharko litzateke.$\rGTK+ exekuzio-ingurunearen ${GTK_MIN_VERSION} bertsioa edo berriagoa instalatu" - -; License Page -!define PIDGIN_LICENSE_BUTTON "Jarraitu >" -!define PIDGIN_LICENSE_BOTTOM_TEXT "GNU Lizentzia Orokor Publikopean (GPL) argitaratzen da $(^Name). Informatzeko helburu soilarekin aurkezten da hemen lizentzia. $_CLICK" - -; Components Page -!define PIDGIN_SECTION_TITLE "Pidgin Istanteko Mezularitza Bezeroa (beharrezkoa)" -!define GTK_SECTION_TITLE "GTK+ exekuzio ingurunea (beharrezkoa)" -!define PIDGIN_SHORTCUTS_SECTION_TITLE "Lasterbideak" -!define PIDGIN_DESKTOP_SHORTCUT_SECTION_TITLE "Mahaigaina" -!define PIDGIN_STARTMENU_SHORTCUT_SECTION_TITLE "Abio-menua" -!define PIDGIN_SECTION_DESCRIPTION "Funtsezko Pidgin fitxategi eta dll-ak" -!define GTK_SECTION_DESCRIPTION "Plataforma anitzeko GUI tresna-sorta, Pidgin-ek erabilia" - -!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION "Pidgin abiarazteko lasterbideak" -!define PIDGIN_DESKTOP_SHORTCUT_DESC "Pidgin-entzako lasterbidea Mahaigainean" -!define PIDGIN_STARTMENU_SHORTCUT_DESC "Pidgin-entzako lasterbidea Abio-Menuan" - -; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "GTK+ exekuzio-ingurunearen bertsio zahar bat aurkitu da. Eguneratu egin nahi al duzu?$\rOharra: Bestela, posible da $(^Name) ez ibiltzea." - -; Installer Finish Page -!define PIDGIN_FINISH_VISIT_WEB_SITE "Pidgin Webgunera etorri" - -; Pidgin Section Prompts and Texts -!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Ezin izan da jadanik instalatuta zegoen Pidgin bertsioa kendu. Aurreko bertsioa kendu gabe instalatuko da bertsio berria." - -; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Errorea GTK+ exekuzio-ingurunea instalatzean." -!define GTK_BAD_INSTALL_PATH "The path you entered can not be accessed or created." - -; URL Handler section -!define URI_HANDLERS_SECTION_TITLE "URI Kudeatzaileak" - -; Uninstall Section Prompts -!define un.PIDGIN_UNINSTALL_ERROR_1 "Ezin izan dira Pidgin-en erregistro-sarrerak aurkitu.$\rZiurrenik, beste erabiltzaile batek instalatu zuen aplikazio hau." -!define un.PIDGIN_UNINSTALL_ERROR_2 "Ez daukazu aplikazio hau kentzeko baimenik." - -; Spellcheck Section Prompts -!define PIDGIN_SPELLCHECK_SECTION_TITLE "Zuzentzaile Ortografikoa" -!define PIDGIN_SPELLCHECK_ERROR "Errorea Zuzentzaile Ortografikoa instalatzean" -!define PIDGIN_SPELLCHECK_DICT_ERROR "Errorea Zuzentzaile Ortografikoarentzako hiztegia instalatzean" -!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION "Zuzentzaile Ortografikoa. (Internet konexioa behar du instalatzeko)" -!define ASPELL_INSTALL_FAILED "Ezin izan da instalatu" -!define PIDGIN_SPELLCHECK_BRETON "Britaniera" -!define PIDGIN_SPELLCHECK_CATALAN "Katalana" -!define PIDGIN_SPELLCHECK_CZECH "Txekiera" -!define PIDGIN_SPELLCHECK_WELSH "Gaelikoa" -!define PIDGIN_SPELLCHECK_DANISH "Daniera" -!define PIDGIN_SPELLCHECK_GERMAN "Alemana" -!define PIDGIN_SPELLCHECK_GREEK "Grekoa" -!define PIDGIN_SPELLCHECK_ENGLISH "Ingelesa" -!define PIDGIN_SPELLCHECK_ESPERANTO "Esperantoa" -!define PIDGIN_SPELLCHECK_SPANISH "Gaztelania" -!define PIDGIN_SPELLCHECK_FAROESE "Faroera" -!define PIDGIN_SPELLCHECK_FRENCH "Frantsesa" -!define PIDGIN_SPELLCHECK_ITALIAN "Italiera" -!define PIDGIN_SPELLCHECK_DUTCH "Nederlandera" -!define PIDGIN_SPELLCHECK_NORWEGIAN "Norvegiera" -!define PIDGIN_SPELLCHECK_POLISH "Poloniera" -!define PIDGIN_SPELLCHECK_PORTUGUESE "Portugesa" -!define PIDGIN_SPELLCHECK_ROMANIAN "Errumaniera" -!define PIDGIN_SPELLCHECK_RUSSIAN "Errusiera" -!define PIDGIN_SPELLCHECK_SLOVAK "Eslovakiera" -!define PIDGIN_SPELLCHECK_SWEDISH "Suediera" -!define PIDGIN_SPELLCHECK_UKRAINIAN "Ukraniera" - +;; +;; basque.nsh +;;Abio-menua - Istanteko Mezularitza +;; Basque language strings for the Windows Pidgin NSIS installer. +;; Windows Code page: 1252 +;; +;; Author: Mikel Pascual Aldabaldetreku <mikel.paskual@gmail.com>, 2007. + +; Startup Checks +!define INSTALLER_IS_RUNNING "Instalatzailea martxan dago." +!define PIDGIN_IS_RUNNING "Pidgin istantzia bat dago martxan. Pidgin itxi eta berriro saiatu." + +; License Page +!define PIDGIN_LICENSE_BUTTON "Jarraitu >" +!define PIDGIN_LICENSE_BOTTOM_TEXT "GNU Lizentzia Orokor Publikopean (GPL) argitaratzen da $(^Name). Informatzeko helburu soilarekin aurkezten da hemen lizentzia. $_CLICK" + +; Components Page +!define PIDGIN_SECTION_TITLE "Pidgin Istanteko Mezularitza Bezeroa (beharrezkoa)" +!define GTK_SECTION_TITLE "GTK+ exekuzio ingurunea (beharrezkoa)" +!define PIDGIN_SHORTCUTS_SECTION_TITLE "Lasterbideak" +!define PIDGIN_DESKTOP_SHORTCUT_SECTION_TITLE "Mahaigaina" +!define PIDGIN_STARTMENU_SHORTCUT_SECTION_TITLE "Abio-menua" +!define PIDGIN_SECTION_DESCRIPTION "Funtsezko Pidgin fitxategi eta dll-ak" +!define GTK_SECTION_DESCRIPTION "Plataforma anitzeko GUI tresna-sorta, Pidgin-ek erabilia" + +!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION "Pidgin abiarazteko lasterbideak" +!define PIDGIN_DESKTOP_SHORTCUT_DESC "Pidgin-entzako lasterbidea Mahaigainean" +!define PIDGIN_STARTMENU_SHORTCUT_DESC "Pidgin-entzako lasterbidea Abio-Menuan" + +; GTK+ Directory Page + +; Installer Finish Page +!define PIDGIN_FINISH_VISIT_WEB_SITE "Pidgin Webgunera etorri" + +; Pidgin Section Prompts and Texts +!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Ezin izan da jadanik instalatuta zegoen Pidgin bertsioa kendu. Aurreko bertsioa kendu gabe instalatuko da bertsio berria." + +; GTK+ Section Prompts + +; URL Handler section +!define URI_HANDLERS_SECTION_TITLE "URI Kudeatzaileak" + +; Uninstall Section Prompts +!define un.PIDGIN_UNINSTALL_ERROR_1 "Ezin izan dira Pidgin-en erregistro-sarrerak aurkitu.$\rZiurrenik, beste erabiltzaile batek instalatu zuen aplikazio hau." +!define un.PIDGIN_UNINSTALL_ERROR_2 "Ez daukazu aplikazio hau kentzeko baimenik." + +; Spellcheck Section Prompts +!define PIDGIN_SPELLCHECK_SECTION_TITLE "Zuzentzaile Ortografikoa" +!define PIDGIN_SPELLCHECK_ERROR "Errorea Zuzentzaile Ortografikoa instalatzean" +!define PIDGIN_SPELLCHECK_DICT_ERROR "Errorea Zuzentzaile Ortografikoarentzako hiztegia instalatzean" +!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION "Zuzentzaile Ortografikoa. (Internet konexioa behar du instalatzeko)" +!define ASPELL_INSTALL_FAILED "Ezin izan da instalatu" +!define PIDGIN_SPELLCHECK_BRETON "Britaniera" +!define PIDGIN_SPELLCHECK_CATALAN "Katalana" +!define PIDGIN_SPELLCHECK_CZECH "Txekiera" +!define PIDGIN_SPELLCHECK_WELSH "Gaelikoa" +!define PIDGIN_SPELLCHECK_DANISH "Daniera" +!define PIDGIN_SPELLCHECK_GERMAN "Alemana" +!define PIDGIN_SPELLCHECK_GREEK "Grekoa" +!define PIDGIN_SPELLCHECK_ENGLISH "Ingelesa" +!define PIDGIN_SPELLCHECK_ESPERANTO "Esperantoa" +!define PIDGIN_SPELLCHECK_SPANISH "Gaztelania" +!define PIDGIN_SPELLCHECK_FAROESE "Faroera" +!define PIDGIN_SPELLCHECK_FRENCH "Frantsesa" +!define PIDGIN_SPELLCHECK_ITALIAN "Italiera" +!define PIDGIN_SPELLCHECK_DUTCH "Nederlandera" +!define PIDGIN_SPELLCHECK_NORWEGIAN "Norvegiera" +!define PIDGIN_SPELLCHECK_POLISH "Poloniera" +!define PIDGIN_SPELLCHECK_PORTUGUESE "Portugesa" +!define PIDGIN_SPELLCHECK_ROMANIAN "Errumaniera" +!define PIDGIN_SPELLCHECK_RUSSIAN "Errusiera" +!define PIDGIN_SPELLCHECK_SLOVAK "Eslovakiera" +!define PIDGIN_SPELLCHECK_SWEDISH "Suediera" +!define PIDGIN_SPELLCHECK_UKRAINIAN "Ukraniera" +
--- a/pidgin/win32/nsis/translations/bulgarian.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/bulgarian.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -8,9 +8,6 @@ ;; -; Startup GTK+ check -!define GTK_INSTALLER_NEEDED "GTK+ runtime .$\r v${GTK_MIN_VERSION} -" - ; Components Page !define PIDGIN_SECTION_TITLE "Pidgin ( )" !define GTK_SECTION_TITLE "GTK+ Runtime (required)" @@ -19,11 +16,8 @@ ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT " GTK+ runtime . ?$\rNote: Pidgin ." ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR " GTK+ runtime." -!define GTK_BAD_INSTALL_PATH " ." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 " Pidgin.$\r ."
--- a/pidgin/win32/nsis/translations/catalan.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/catalan.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -12,7 +12,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING "L'instal.lador encara est executant-se." !define PIDGIN_IS_RUNNING "Hi ha una instncia del Pidgin executant-se. Surt del Pidgin i torna a intentar-ho." -!define GTK_INSTALLER_NEEDED "L'entorn d'execuci GTK+ no existeix o necessita sser actualitzat.$\rSius plau instal.la la versi${GTK_MIN_VERSION} o superior de l'entonr GTK+" ; License Page !define PIDGIN_LICENSE_BUTTON "Segent >" @@ -34,14 +33,11 @@ ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "S'ha trobat una versi antiga de l'entorn d'execuci GTK. Vols actualitzar-la?$\rNota: $(^Name) no funcionar sino ho fas." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Visita la pgina web de Pidgin per Windows" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Error installlant l'entorn d'execuci GTK+." -!define GTK_BAD_INSTALL_PATH "El directori que has introdut no pot sser accedit o creat." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "L'instal.lador podria no trobar les entrades del registre de Pidgin.$\rProbablement un altre usuari ha instal.lat aquesta aplicaci."
--- a/pidgin/win32/nsis/translations/czech.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/czech.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -8,8 +8,6 @@ ;; Version 2 ;; -; Startup GTK+ check -!define GTK_INSTALLER_NEEDED "GTK+ runtime buto chyb, nebo je poteba provst upgrade.$\rProvete instalaci verze${GTK_MIN_VERSION} nebo vy." ; License Page !define PIDGIN_LICENSE_BUTTON "Dal >" @@ -23,14 +21,11 @@ ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Byla nalezena star verze GTK+ runtime. Chcete provst upgrade?$\rUpozornn: Bez upgradu $(^Name) nemus pracovat sprvn." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Navtvit Windows Pidgin Web Page" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Chyba pi instalaci GTK+ runtime." -!define GTK_BAD_INSTALL_PATH "Zadan cesta je nedostupn, nebo ji nelze vytvoit." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "Odinstaln proces neme najt zznamy pro Pidgin v registrech.$\rPravdpodobn instalaci tto aplikace provedl jin uivatel."
--- a/pidgin/win32/nsis/translations/danish.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/danish.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -8,9 +8,6 @@ ;; Version 2 ;; -; Startup GTK+ check -!define GTK_INSTALLER_NEEDED "GTK+ runtime environment enten mangler eller skal opgraderes.$\rInstallr venligst GTK+ runtime version v${GTK_MIN_VERSION} eller hjere." - ; License Page !define PIDGIN_LICENSE_BUTTON "Nste >" !define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) er frigivet under GPL licensen. Licensen er kun medtaget her til generel orientering. $_CLICK" @@ -23,14 +20,11 @@ ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Der blev fundet en ldre version af GTK+ runtime. nsker du at opgradere?$\rNB: $(^Name) virker muligvis ikke uden denne opgradering." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Besg Windows Pidgin's hjemmeside" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Fejl under installeringen af GTK+ runtime." -!define GTK_BAD_INSTALL_PATH "Stien du har angivet kan ikke findes eller oprettes." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "Afinstallationen kunne ikke finde Pidgin i registreringsdatabasen.$\rMuligvis har en anden bruger installeret programmet."
--- a/pidgin/win32/nsis/translations/dutch.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/dutch.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -11,7 +11,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING "Er is al een installatie actief." !define PIDGIN_IS_RUNNING "Pidgin wordt op dit moment uitgevoerd. Sluit Pidgin af en start de installatie opnieuw." -!define GTK_INSTALLER_NEEDED "De GTK+ runtime-omgeving is niet aanwezig of moet vernieuwd worden.$\rInstalleer v${GTK_MIN_VERSION} of nieuwer van de GTK+ runtime-omgeving" ; License Page @@ -26,14 +25,11 @@ ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Er is een oude versie van GTK+ gevonden. Wilt u deze bijwerken?$\rLet op: $(^Name) werkt misschien niet als u dit niet doet." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Neem een kijkje op de Windows Pidgin webpagina" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Fout bij installatie van GTK+ runtime omgeving." -!define GTK_BAD_INSTALL_PATH "Het door u gegeven pad kan niet benaderd worden." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "Het verwijderingsprogramma voor Pidgin kon geen register-ingangen voor Pidgin vinden.$\rWaarschijnlijk heeft een andere gebruiker het programma genstalleerd."
--- a/pidgin/win32/nsis/translations/english.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/english.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -14,7 +14,6 @@ ; Startup Checks !insertmacro PIDGIN_MACRO_DEFAULT_STRING INSTALLER_IS_RUNNING "The installer is already running." !insertmacro PIDGIN_MACRO_DEFAULT_STRING PIDGIN_IS_RUNNING "An instance of Pidgin is currently running. Please exit Pidgin and try again." -!insertmacro PIDGIN_MACRO_DEFAULT_STRING GTK_INSTALLER_NEEDED "The GTK+ runtime environment is either missing or needs to be upgraded.$\rPlease install v${GTK_MIN_VERSION} or higher of the GTK+ runtime" ; License Page !insertmacro PIDGIN_MACRO_DEFAULT_STRING PIDGIN_LICENSE_BUTTON "Next >" @@ -32,9 +31,9 @@ !insertmacro PIDGIN_MACRO_DEFAULT_STRING PIDGIN_SHORTCUTS_SECTION_DESCRIPTION "Shortcuts for starting Pidgin" !insertmacro PIDGIN_MACRO_DEFAULT_STRING PIDGIN_DESKTOP_SHORTCUT_DESC "Create a shortcut to Pidgin on the Desktop" !insertmacro PIDGIN_MACRO_DEFAULT_STRING PIDGIN_STARTMENU_SHORTCUT_DESC "Create a Start Menu entry for Pidgin" +!insertmacro PIDGIN_MACRO_DEFAULT_STRING DEBUG_SYMBOLS_SECTION_TITLE "Debug Symbols (for reporting crashes)" ; GTK+ Directory Page -!insertmacro PIDGIN_MACRO_DEFAULT_STRING GTK_UPGRADE_PROMPT "An old version of the GTK+ runtime was found. Do you wish to upgrade?$\rNote: $(^Name) may not work unless you do." ; Installer Finish Page !insertmacro PIDGIN_MACRO_DEFAULT_STRING PIDGIN_FINISH_VISIT_WEB_SITE "Visit the Pidgin Web Page" @@ -43,8 +42,6 @@ !insertmacro PIDGIN_MACRO_DEFAULT_STRING PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Unable to uninstall the currently installed version of Pidgin. The new version will be installed without removing the currently installed version." ; GTK+ Section Prompts -!insertmacro PIDGIN_MACRO_DEFAULT_STRING GTK_INSTALL_ERROR "Error installing GTK+ runtime." -!insertmacro PIDGIN_MACRO_DEFAULT_STRING GTK_BAD_INSTALL_PATH "The path you entered can not be accessed or created." ; URL Handler section !insertmacro PIDGIN_MACRO_DEFAULT_STRING URI_HANDLERS_SECTION_TITLE "URI Handlers" @@ -82,3 +79,6 @@ !insertmacro PIDGIN_MACRO_DEFAULT_STRING PIDGIN_SPELLCHECK_SWEDISH "Swedish" !insertmacro PIDGIN_MACRO_DEFAULT_STRING PIDGIN_SPELLCHECK_UKRAINIAN "Ukrainian" +!insertmacro PIDGIN_MACRO_DEFAULT_STRING PIDGIN_DEBUGSYMBOLS_ERROR "Error Installing Debug Symbols" + +!insertmacro PIDGIN_MACRO_DEFAULT_STRING PIDGIN_GTK_DOWNLOAD_ERROR "Error Downloading the GTK+ Runtime"
--- a/pidgin/win32/nsis/translations/finnish.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/finnish.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -13,7 +13,6 @@ ; Startup checks !define INSTALLER_IS_RUNNING "Asennusohjelma on jo kynniss." !define PIDGIN_IS_RUNNING "Pidgin on tll hetkell kynniss. Poistu Pidginist ja yrit uudelleen." -!define GTK_INSTALLER_NEEDED "Ajonaikainen GTK+-ymprist joko puuttuu tai tarvitsee pivityst.$\rOle hyv ja asenna v${GTK_MIN_VERSION} tai uudempi ajonaikainen GTK+-ymprist." ; License Page !define PIDGIN_LICENSE_BUTTON "Seuraava >" @@ -33,14 +32,11 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "Tee Pidgin-pikakuvake kynnistysvalikkoon" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Vanha versio ajonaikaisesta GTK+-ympristst lytynyt. Tahdotko pivitt?$\rHuomio: $(^Name) ei vlttmtt toimi mikli jtt pivittmtt." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Vieraile Pidginin WWW-sivustolla" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Virhe asennettaessa ajonaikaista GTK+-ymprist." -!define GTK_BAD_INSTALL_PATH "Antamasi polku ei toimi tai sit ei voi luoda." ; URL Handler section !define URI_HANDLERS_SECTION_TITLE "URI-ksittelijt"
--- a/pidgin/win32/nsis/translations/french.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/french.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -1,4 +1,3 @@ -;; vim:syn=winbatch:fileencoding=cp1252: ;; ;; french.nsh ;; @@ -15,7 +14,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING "Le programme d'installation est dj en cours d'excution." !define PIDGIN_IS_RUNNING "Une instance de Pidgin est en cours d'excution. Veuillez quitter Pidgin et ressayer." -!define GTK_INSTALLER_NEEDED "Les bibliothques de l'environnement GTK+ ne sont pas installes ou ont besoin d'une mise jour.$\rVeuillez installer la version ${GTK_MIN_VERSION} ou plus rcente des bibliothques GTK+." ; License Page !define PIDGIN_LICENSE_BUTTON "Suivant >" @@ -35,7 +33,6 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "Crer un raccourci pour Pidgin dans le menu Dmarrer" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Une ancienne version des bibliothques GTK+ a t trouve. Voulez-vous la mettre jour ?$\rNote : $(^Name) peut ne pas fonctionner si vous ne le faites pas." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Visitez la page web de Pidgin Windows" @@ -44,8 +41,6 @@ !define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Impossible de dsinstaller la version de Pidgin en place. La nouvelle version sera installe sans supprimer la version en place." ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Erreur lors de l'installation des bibliothques GTK+" -!define GTK_BAD_INSTALL_PATH "Le dossier d'installation ne peut pas tre cr ou n'est pas accessible." ; URL Handler section !define URI_HANDLERS_SECTION_TITLE "Gestion des liens (URI)"
--- a/pidgin/win32/nsis/translations/galician.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/galician.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -9,7 +9,6 @@ ;; ; Startup GTK+ check -!define GTK_INSTALLER_NEEDED "O entorno de execucin de GTK+ falta ou necesita ser actualizado.$\rPor favor, instale a versin v${GTK_MIN_VERSION} do executable GTK+ ou algunha posterior." ; License Page !define PIDGIN_LICENSE_BUTTON "Seguinte >" @@ -23,14 +22,11 @@ ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Atopouse unha versin antiga do executable de GTK+. Desexa actualizala?$\rObservacin: $(^Name) non funcionar a menos que o faga." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Visite a pxina Web de Pidgin Windows" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Erro ao instalar o executable GTK+." -!define GTK_BAD_INSTALL_PATH "Non se puido acceder ou crear a ruta que vd. indicou." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "O desinstalador non puido atopar as entradas no rexistro de Pidgin.$\r probable que outro usuario instalara a aplicacin."
--- a/pidgin/win32/nsis/translations/german.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/german.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -1,81 +1,77 @@ -;; vim:syn=winbatch:encoding=cp1252: -;; -;; german.nsh -;; -;; German language strings for the Windows Pidgin NSIS installer. -;; Windows Code page: 1252 -;; -;; Author: Bjoern Voigt <bjoern@cs.tu-berlin.de>, 2008. -;; Version 3 -;; - -; Startup checks -!define INSTALLER_IS_RUNNING "Der Installer luft schon." -!define PIDGIN_IS_RUNNING "Eine Instanz von Pidgin luft momentan schon. Beenden Sie Pidgin und versuchen Sie es nochmal." -!define GTK_INSTALLER_NEEDED "Die GTK+ Runtime Umgebung fehlt entweder oder muss aktualisiert werden.$\rBitte installieren Sie v${GTK_MIN_VERSION} oder hher der GTK+ Runtime" - -; License Page -!define PIDGIN_LICENSE_BUTTON "Weiter >" -!define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) wird unter der GNU General Public License (GPL) verffentlicht. Die Lizenz dient hier nur der Information. $_CLICK" - -; Components Page -!define PIDGIN_SECTION_TITLE "Pidgin Instant Messaging Client (erforderlich)" -!define GTK_SECTION_TITLE "GTK+ Runtime Umgebung (erforderlich)" -!define PIDGIN_SHORTCUTS_SECTION_TITLE "Verknpfungen" -!define PIDGIN_DESKTOP_SHORTCUT_SECTION_TITLE "Desktop" -!define PIDGIN_STARTMENU_SHORTCUT_SECTION_TITLE "Startmen" -!define PIDGIN_SECTION_DESCRIPTION "Pidgin-Basisdateien und -DLLs" -!define GTK_SECTION_DESCRIPTION "Ein Multi-Plattform-GUI-Toolkit, verwendet von Pidgin" - -!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION "Verknpfungen zum Starten von Pidgin" -!define PIDGIN_DESKTOP_SHORTCUT_DESC "Erstellt eine Verknpfung zu Pidgin auf dem Desktop" -!define PIDGIN_STARTMENU_SHORTCUT_DESC "Erstellt einen Eintrag fr Pidgin im Startmen" - -; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Eine alte Version der GTK+ Runtime wurde gefunden. Mchten Sie aktualisieren?$\rHinweis: $(^Name) funktioniert evtl. nicht, wenn Sie nicht aktualisieren." - -; Installer Finish Page -!define PIDGIN_FINISH_VISIT_WEB_SITE "Besuchen Sie die Pidgin Webseite" - -; Pidgin Section Prompts and Texts -!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Die aktuell installierte Version von Pidgin kann nicht deinstalliert werden. Die neue Version wird installiert, ohne dass die aktuell installierte Version gelscht wird." - -; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Fehler beim Installieren der GTK+ Runtime." -!define GTK_BAD_INSTALL_PATH "Der Pfad, den Sie eingegeben haben, existiert nicht und kann nicht erstellt werden." - -; URL Handler section -!define URI_HANDLERS_SECTION_TITLE "URI-Behandlung" - -; Uninstall Section Prompts -!define un.PIDGIN_UNINSTALL_ERROR_1 "Der Deinstaller konnte keine Registrierungsschlssel fr Pidgin finden.$\rEs ist wahrscheinlich, da ein anderer Benutzer diese Anwendung installiert hat." -!define un.PIDGIN_UNINSTALL_ERROR_2 "Sie haben keine Berechtigung, diese Anwendung zu deinstallieren." - -; Spellcheck Section Prompts -!define PIDGIN_SPELLCHECK_SECTION_TITLE "Untersttzung fr Rechtschreibkontrolle" -!define PIDGIN_SPELLCHECK_ERROR "Fehler bei der Installation der Rechtschreibkontrolle" -!define PIDGIN_SPELLCHECK_DICT_ERROR "Fehler bei der Installation des Wrterbuches fr die Rechtschreibkontrolle" -!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION "Untersttzung fr Rechtschreibkontrolle. (Fr die Installation ist eine Internet-Verbindung ntig)" -!define ASPELL_INSTALL_FAILED "Installation gescheitert" -!define PIDGIN_SPELLCHECK_BRETON "Bretonisch" -!define PIDGIN_SPELLCHECK_CATALAN "Katalanisch" -!define PIDGIN_SPELLCHECK_CZECH "Tschechisch" -!define PIDGIN_SPELLCHECK_WELSH "Walisisch" -!define PIDGIN_SPELLCHECK_DANISH "Dnisch" -!define PIDGIN_SPELLCHECK_GERMAN "Deutsch" -!define PIDGIN_SPELLCHECK_GREEK "Griechisch" -!define PIDGIN_SPELLCHECK_ENGLISH "Englisch" -!define PIDGIN_SPELLCHECK_ESPERANTO "Esperanto" -!define PIDGIN_SPELLCHECK_SPANISH "Spanisch" -!define PIDGIN_SPELLCHECK_FAROESE "Farersprache" -!define PIDGIN_SPELLCHECK_FRENCH "Franzsisch" -!define PIDGIN_SPELLCHECK_ITALIAN "Italienisch" -!define PIDGIN_SPELLCHECK_DUTCH "Hollndisch" -!define PIDGIN_SPELLCHECK_NORWEGIAN "Norwegisch" -!define PIDGIN_SPELLCHECK_POLISH "Polnisch" -!define PIDGIN_SPELLCHECK_PORTUGUESE "Portugiesisch" -!define PIDGIN_SPELLCHECK_ROMANIAN "Rumnisch" -!define PIDGIN_SPELLCHECK_RUSSIAN "Russisch" -!define PIDGIN_SPELLCHECK_SLOVAK "Slowakisch" -!define PIDGIN_SPELLCHECK_SWEDISH "Schwedisch" -!define PIDGIN_SPELLCHECK_UKRAINIAN "Ukrainisch" +;; vim:syn=winbatch:encoding=cp1252: +;; +;; german.nsh +;; +;; German language strings for the Windows Pidgin NSIS installer. +;; Windows Code page: 1252 +;; +;; Author: Bjoern Voigt <bjoern@cs.tu-berlin.de>, 2008. +;; Version 3 +;; + +; Startup checks +!define INSTALLER_IS_RUNNING "Der Installer luft schon." +!define PIDGIN_IS_RUNNING "Eine Instanz von Pidgin luft momentan schon. Beenden Sie Pidgin und versuchen Sie es nochmal." + +; License Page +!define PIDGIN_LICENSE_BUTTON "Weiter >" +!define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) wird unter der GNU General Public License (GPL) verffentlicht. Die Lizenz dient hier nur der Information. $_CLICK" + +; Components Page +!define PIDGIN_SECTION_TITLE "Pidgin Instant Messaging Client (erforderlich)" +!define GTK_SECTION_TITLE "GTK+ Runtime Umgebung (erforderlich)" +!define PIDGIN_SHORTCUTS_SECTION_TITLE "Verknpfungen" +!define PIDGIN_DESKTOP_SHORTCUT_SECTION_TITLE "Desktop" +!define PIDGIN_STARTMENU_SHORTCUT_SECTION_TITLE "Startmen" +!define PIDGIN_SECTION_DESCRIPTION "Pidgin-Basisdateien und -DLLs" +!define GTK_SECTION_DESCRIPTION "Ein Multi-Plattform-GUI-Toolkit, verwendet von Pidgin" + +!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION "Verknpfungen zum Starten von Pidgin" +!define PIDGIN_DESKTOP_SHORTCUT_DESC "Erstellt eine Verknpfung zu Pidgin auf dem Desktop" +!define PIDGIN_STARTMENU_SHORTCUT_DESC "Erstellt einen Eintrag fr Pidgin im Startmen" + +; GTK+ Directory Page + +; Installer Finish Page +!define PIDGIN_FINISH_VISIT_WEB_SITE "Besuchen Sie die Pidgin Webseite" + +; Pidgin Section Prompts and Texts +!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Die aktuell installierte Version von Pidgin kann nicht deinstalliert werden. Die neue Version wird installiert, ohne dass die aktuell installierte Version gelscht wird." + +; GTK+ Section Prompts + +; URL Handler section +!define URI_HANDLERS_SECTION_TITLE "URI-Behandlung" + +; Uninstall Section Prompts +!define un.PIDGIN_UNINSTALL_ERROR_1 "Der Deinstaller konnte keine Registrierungsschlssel fr Pidgin finden.$\rEs ist wahrscheinlich, da ein anderer Benutzer diese Anwendung installiert hat." +!define un.PIDGIN_UNINSTALL_ERROR_2 "Sie haben keine Berechtigung, diese Anwendung zu deinstallieren." + +; Spellcheck Section Prompts +!define PIDGIN_SPELLCHECK_SECTION_TITLE "Untersttzung fr Rechtschreibkontrolle" +!define PIDGIN_SPELLCHECK_ERROR "Fehler bei der Installation der Rechtschreibkontrolle" +!define PIDGIN_SPELLCHECK_DICT_ERROR "Fehler bei der Installation des Wrterbuches fr die Rechtschreibkontrolle" +!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION "Untersttzung fr Rechtschreibkontrolle. (Fr die Installation ist eine Internet-Verbindung ntig)" +!define ASPELL_INSTALL_FAILED "Installation gescheitert" +!define PIDGIN_SPELLCHECK_BRETON "Bretonisch" +!define PIDGIN_SPELLCHECK_CATALAN "Katalanisch" +!define PIDGIN_SPELLCHECK_CZECH "Tschechisch" +!define PIDGIN_SPELLCHECK_WELSH "Walisisch" +!define PIDGIN_SPELLCHECK_DANISH "Dnisch" +!define PIDGIN_SPELLCHECK_GERMAN "Deutsch" +!define PIDGIN_SPELLCHECK_GREEK "Griechisch" +!define PIDGIN_SPELLCHECK_ENGLISH "Englisch" +!define PIDGIN_SPELLCHECK_ESPERANTO "Esperanto" +!define PIDGIN_SPELLCHECK_SPANISH "Spanisch" +!define PIDGIN_SPELLCHECK_FAROESE "Farersprache" +!define PIDGIN_SPELLCHECK_FRENCH "Franzsisch" +!define PIDGIN_SPELLCHECK_ITALIAN "Italienisch" +!define PIDGIN_SPELLCHECK_DUTCH "Hollndisch" +!define PIDGIN_SPELLCHECK_NORWEGIAN "Norwegisch" +!define PIDGIN_SPELLCHECK_POLISH "Polnisch" +!define PIDGIN_SPELLCHECK_PORTUGUESE "Portugiesisch" +!define PIDGIN_SPELLCHECK_ROMANIAN "Rumnisch" +!define PIDGIN_SPELLCHECK_RUSSIAN "Russisch" +!define PIDGIN_SPELLCHECK_SLOVAK "Slowakisch" +!define PIDGIN_SPELLCHECK_SWEDISH "Schwedisch" +!define PIDGIN_SPELLCHECK_UKRAINIAN "Ukrainisch"
--- a/pidgin/win32/nsis/translations/hebrew.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/hebrew.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -1,82 +1,78 @@ -;; -;; hebrew.nsh -;; -;; Hebrew language strings for the Windows Pidgin NSIS installer. -;; Windows Code page: 1255 -;; -;; Updated: Shalom Craimer <scraimer@gmail.com> -;; Origional Author: Eugene Shcherbina <eugene@websterworlds.com> -;; Version 3 -;; - -; Startup checks -!define INSTALLER_IS_RUNNING " ." -!define PIDGIN_IS_RUNNING " ' . ' ." -!define GTK_INSTALLER_NEEDED ". GTK+ $\r v${GTK_MIN_VERSION} .GTK+ " - -; License Page -!define PIDGIN_LICENSE_BUTTON " >" -!define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) . .GPL $_CLICK" - -; Components Page -!define PIDGIN_SECTION_TITLE "() .Pidgin " -!define GTK_SECTION_TITLE "() .GTK+ " -!define PIDGIN_SHORTCUTS_SECTION_TITLE " " -!define PIDGIN_DESKTOP_SHORTCUT_SECTION_TITLE " " -!define PIDGIN_STARTMENU_SHORTCUT_SECTION_TITLE " " -!define PIDGIN_SECTION_DESCRIPTION ". DLL- Pidgin " -!define GTK_SECTION_DESCRIPTION "-, ' GUI " - -!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION "- '" -!define PIDGIN_DESKTOP_SHORTCUT_DESC " - ' " -!define PIDGIN_STARTMENU_SHORTCUT_DESC " - ' " - -; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "A?. GTK+ $\rNote: . $(^Name)" - -; Installer Finish Page -!define PIDGIN_FINISH_VISIT_WEB_SITE ".Pidgin " - -; Pidgin Section Prompts and Texts -!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL " '. ." - -; GTK+ Section Prompts -!define GTK_INSTALL_ERROR ".GTK+ " -!define GTK_BAD_INSTALL_PATH ". " - -; URL Handler section -!define URI_HANDLERS_SECTION_TITLE " URI" - -; Uninstall Section Prompts -!define un.PIDGIN_UNINSTALL_ERROR_1 ".GTK+ $\r. " -!define un.PIDGIN_UNINSTALL_ERROR_2 ". " - -; Spellcheck Section Prompts -!define PIDGIN_SPELLCHECK_SECTION_TITLE " " -!define PIDGIN_SPELLCHECK_ERROR " " -!define PIDGIN_SPELLCHECK_DICT_ERROR " " -!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION " ( )" -!define ASPELL_INSTALL_FAILED " " -!define PIDGIN_SPELLCHECK_BRETON "" -!define PIDGIN_SPELLCHECK_CATALAN "" -!define PIDGIN_SPELLCHECK_CZECH "'" -!define PIDGIN_SPELLCHECK_WELSH "" -!define PIDGIN_SPELLCHECK_DANISH "" -!define PIDGIN_SPELLCHECK_GERMAN "" -!define PIDGIN_SPELLCHECK_GREEK "" -!define PIDGIN_SPELLCHECK_ENGLISH "" -!define PIDGIN_SPELLCHECK_ESPERANTO "" -!define PIDGIN_SPELLCHECK_SPANISH "" -!define PIDGIN_SPELLCHECK_FAROESE "" -!define PIDGIN_SPELLCHECK_FRENCH "" -!define PIDGIN_SPELLCHECK_ITALIAN "" -!define PIDGIN_SPELLCHECK_DUTCH "" -!define PIDGIN_SPELLCHECK_NORWEGIAN "" -!define PIDGIN_SPELLCHECK_POLISH "" -!define PIDGIN_SPELLCHECK_PORTUGUESE "" -!define PIDGIN_SPELLCHECK_ROMANIAN "" -!define PIDGIN_SPELLCHECK_RUSSIAN "" -!define PIDGIN_SPELLCHECK_SLOVAK "" -!define PIDGIN_SPELLCHECK_SWEDISH "" -!define PIDGIN_SPELLCHECK_UKRAINIAN "" - +;; +;; hebrew.nsh +;; +;; Hebrew language strings for the Windows Pidgin NSIS installer. +;; Windows Code page: 1255 +;; +;; Updated: Shalom Craimer <scraimer@gmail.com> +;; Origional Author: Eugene Shcherbina <eugene@websterworlds.com> +;; Version 3 +;; + +; Startup checks +!define INSTALLER_IS_RUNNING " ." +!define PIDGIN_IS_RUNNING " ' . ' ." + +; License Page +!define PIDGIN_LICENSE_BUTTON " >" +!define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) . .GPL $_CLICK" + +; Components Page +!define PIDGIN_SECTION_TITLE "() .Pidgin " +!define GTK_SECTION_TITLE "() .GTK+ " +!define PIDGIN_SHORTCUTS_SECTION_TITLE " " +!define PIDGIN_DESKTOP_SHORTCUT_SECTION_TITLE " " +!define PIDGIN_STARTMENU_SHORTCUT_SECTION_TITLE " " +!define PIDGIN_SECTION_DESCRIPTION ". DLL- Pidgin " +!define GTK_SECTION_DESCRIPTION "-, ' GUI " + +!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION "- '" +!define PIDGIN_DESKTOP_SHORTCUT_DESC " - ' " +!define PIDGIN_STARTMENU_SHORTCUT_DESC " - ' " + +; GTK+ Directory Page + +; Installer Finish Page +!define PIDGIN_FINISH_VISIT_WEB_SITE ".Pidgin " + +; Pidgin Section Prompts and Texts +!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL " '. ." + +; GTK+ Section Prompts + +; URL Handler section +!define URI_HANDLERS_SECTION_TITLE " URI" + +; Uninstall Section Prompts +!define un.PIDGIN_UNINSTALL_ERROR_1 ".GTK+ $\r. " +!define un.PIDGIN_UNINSTALL_ERROR_2 ". " + +; Spellcheck Section Prompts +!define PIDGIN_SPELLCHECK_SECTION_TITLE " " +!define PIDGIN_SPELLCHECK_ERROR " " +!define PIDGIN_SPELLCHECK_DICT_ERROR " " +!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION " ( )" +!define ASPELL_INSTALL_FAILED " " +!define PIDGIN_SPELLCHECK_BRETON "" +!define PIDGIN_SPELLCHECK_CATALAN "" +!define PIDGIN_SPELLCHECK_CZECH "'" +!define PIDGIN_SPELLCHECK_WELSH "" +!define PIDGIN_SPELLCHECK_DANISH "" +!define PIDGIN_SPELLCHECK_GERMAN "" +!define PIDGIN_SPELLCHECK_GREEK "" +!define PIDGIN_SPELLCHECK_ENGLISH "" +!define PIDGIN_SPELLCHECK_ESPERANTO "" +!define PIDGIN_SPELLCHECK_SPANISH "" +!define PIDGIN_SPELLCHECK_FAROESE "" +!define PIDGIN_SPELLCHECK_FRENCH "" +!define PIDGIN_SPELLCHECK_ITALIAN "" +!define PIDGIN_SPELLCHECK_DUTCH "" +!define PIDGIN_SPELLCHECK_NORWEGIAN "" +!define PIDGIN_SPELLCHECK_POLISH "" +!define PIDGIN_SPELLCHECK_PORTUGUESE "" +!define PIDGIN_SPELLCHECK_ROMANIAN "" +!define PIDGIN_SPELLCHECK_RUSSIAN "" +!define PIDGIN_SPELLCHECK_SLOVAK "" +!define PIDGIN_SPELLCHECK_SWEDISH "" +!define PIDGIN_SPELLCHECK_UKRAINIAN "" +
--- a/pidgin/win32/nsis/translations/hungarian.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/hungarian.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -9,7 +9,6 @@ ;; ; Startup Checks -!define GTK_INSTALLER_NEEDED "A GTK+ futtat krnyezet hinyzik vagy frisstse szksges.$\rKrem teleptse a v${GTK_MIN_VERSION} vagy magasabb verzij GTK+ futtat krnyezetet." !define INSTALLER_IS_RUNNING "A telept mr fut." !define PIDGIN_IS_RUNNING "Jelenleg fut a Pidgin egy pldnya. Lpjen ki a Pidginbl s azutn prblja jra." @@ -31,7 +30,6 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "Start Men bejegyzs ltrehozsa a Pidginhez" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Egy rgi verzij GTK+ futtatkrnyezet van teleptve. Kvnja frissteni?$\rMegjegyzs: a Pidgin nem fog mkdni, ha nem frissti." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "A Windows Pidgin weboldalnak felkeresse" @@ -41,8 +39,6 @@ ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Hiba a GTK+ futtatkrnyezet teleptse kzben." -!define GTK_BAD_INSTALL_PATH "A megadott elrsi t nem rhet el, vagy nem hozhat ltre." !define URI_HANDLERS_SECTION_TITLE "URI kezelk"
--- a/pidgin/win32/nsis/translations/italian.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/italian.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -11,7 +11,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING "Il programma di installazione gi in esecuzione" !define PIDGIN_IS_RUNNING " attualmente in esecuzione un'istanza di Pidgin. Esci da Pidgin e riprova." -!define GTK_INSTALLER_NEEDED "L'ambiente di runtime GTK+ non presente o deve essere aggiornato.$\rInstallare GTK+ versione ${GTK_MIN_VERSION} o maggiore" ; License Page !define PIDGIN_LICENSE_BUTTON "Avanti >" @@ -31,7 +30,6 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "Crea una voce per Pidgin nel Menu Avvio" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT " stata trovata una versione precedente di GTK+. Vuoi aggiornarla?$\rNota: $(^Name) potrebbe non funzionare senza l'aggiornamento." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Visita la pagina web di Pidgin" @@ -40,8 +38,6 @@ !define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Impossibile rimuovere la versione di Pidgin attualmente presente sul tuo computer. La nuova versione sar installata senza rimuovere la versione precedente." ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Error nell'installazione del runtime GTK+." -!define GTK_BAD_INSTALL_PATH "Il percorso scelto non pu essere raggiunto o creato." ; URL Handler section !define URI_HANDLERS_SECTION_TITLE "Gestori degli URI"
--- a/pidgin/win32/nsis/translations/japanese.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/japanese.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -12,7 +12,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING "CXg[ɎsĂ܂" !define PIDGIN_IS_RUNNING "Pidgin sĂ܂BPidgin IĂēxsĂ" -!define GTK_INSTALLER_NEEDED "GTK+^C̓AbvO[hKv܂B$\rv${GTK_MIN_VERSION}͂ȏGTK+^CCXg[ĂB" ; License Page !define PIDGIN_LICENSE_BUTTON " >" @@ -33,14 +32,11 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "X^[gj[ Pidgin ̍ڂ쐬" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Âo[WGTK+^C܂BAbvO[h܂?$\r: $(^Name)̓AbvO[hȂ蓮Ȃł傤B" ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Windows PidginWeby[WKĂB" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "GTK+^C̃CXg[ŃG[܂B" -!define GTK_BAD_INSTALL_PATH "Ȃ̓͂pXɃANZX܂͍쐬ł܂B" ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "ACXg[PidgiñWXgGgł܂łB$\r炭ʂ̃[UɃCXg[ꂽł傤B"
--- a/pidgin/win32/nsis/translations/korean.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/korean.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -7,8 +7,6 @@ ;; Author: Kyung-uk Son <vvs740@chol.com> ;; -; Startup GTK+ check -!define GTK_INSTALLER_NEEDED "GTK+ Ÿ ȯ濡 ְų ̵尡 ʿմϴ.$\rGTK+ Ÿ ȯ v${GTK_MIN_VERSION}̳ ̻ ġּ." ; Components Page !define PIDGIN_SECTION_TITLE " (ʼ)" @@ -17,11 +15,8 @@ !define GTK_SECTION_DESCRIPTION " ϴ Ƽ ÷ GUI Ŷ" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT " GTK+ Ÿ ãҽϴ. ̵ұ?$\rNote: ̵ ֽϴ." ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "GTK+ Ÿ ġ ." -!define GTK_BAD_INSTALL_PATH "ԷϽ ο ų ϴ." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "ν緯 Ʈ Ʈ ã ϴ.$\r α ٸ ġ ϴ."
--- a/pidgin/win32/nsis/translations/kurdish.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/kurdish.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -12,7 +12,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING "Sazker jixwe dimee." !define PIDGIN_IS_RUNNING "Pidgin niha jixwe dimee. Ji Pidgin derkeve careke din biceribne." -!define GTK_INSTALLER_NEEDED "Derdora runtime ya GTK+ an tune an rojanekirina w pwst e. $\rJi kerema xwe v${GTK_MIN_VERSION} an bilindtir a GTK+ saz bike." ; License Page !define PIDGIN_LICENSE_BUTTON "P >" @@ -32,14 +31,11 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "Pidgin binivse menuya destpk" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Guhertoyeke kevn a GTK+ hatiye dtin. Tu dixwaz bilind bik?$\rNot: Heke tu nek, dibe ku $(^Name) naxebite." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Were Malpera Pidgin a Windows" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Di sazkirina GTK+ de ewt derket." -!define GTK_BAD_INSTALL_PATH "rya te nivsand nay gihitin an afirandin." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "Raker tketiyn registry yn Pidgin nedt. $\rQey bikarhnereke din v bername saz kir."
--- a/pidgin/win32/nsis/translations/lithuanian.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/lithuanian.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -9,7 +9,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING "Diegimo programa jau paleista." !define PIDGIN_IS_RUNNING "iuo metu Pidgin yra paleistas. Udarykite i program ir pabandykite i naujo." -!define GTK_INSTALLER_NEEDED "GTK+ vykdymo meto aplinkos nra arba ji turi bti atnaujinta.$\rdiekite v${GTK_MIN_VERSION} arba naujesn GTK+ vykdymo meto aplinkos versij" ; License Page !define PIDGIN_LICENSE_BUTTON "Toliau >" @@ -29,7 +28,6 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "Sukurti pradinio meniu ra, skirt Pidgin." ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Rasta sena GTK+ vykdymo meto aplinkos versija. Ar norite j atnaujinti?$\rPastaba: $(^Name) gali neveikti, jeigu to nepadarysite." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Aplankyti Pidgin tinklalap" @@ -38,8 +36,6 @@ !define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Nepavyko idiegti anksiau diegtos Pidgin versijos. Nauja versija bus diegta neidiegus senosios." ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "GTK+ vykdymo meto aplinkos diegimo klaida" -!define GTK_BAD_INSTALL_PATH "Js vestas kelias negali bti pasiektas ar sukurtas." ; URL Handler section !define URI_HANDLERS_SECTION_TITLE "URI dorokls"
--- a/pidgin/win32/nsis/translations/norwegian.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/norwegian.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -11,7 +11,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING "Installeren kjrer allerede." !define PIDGIN_IS_RUNNING "En instans av Pidgin kjrer fra fr. Avslutt Pidgin og prv igjen." -!define GTK_INSTALLER_NEEDED "GTK+ runtime environment mangler eller trenger en oppgradering.$\rVennligst installr GTK+ v${GTK_MIN_VERSION} eller hyere" ; License Page !define PIDGIN_LICENSE_BUTTON "Neste >" @@ -31,14 +30,11 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "Legg til Pidgin i Startmenyen" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "En eldre versjon av GTK+ runtime ble funnet. nsker du oppgradere?$\rMerk: $(^Name) vil kanskje ikke virke hvis du ikke oppgraderer." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Besk Pidgin for Windows' Nettside" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "En feil oppstod ved installering av GTK+ runtime." -!define GTK_BAD_INSTALL_PATH "Stien du oppga kan ikke aksesseres eller lages." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "Avinstalleringsprogrammet kunne ikke finne noen registeroppfring for Pidgin.$\rTrolig har en annen bruker avinstallert denne applikasjonen."
--- a/pidgin/win32/nsis/translations/persian.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/persian.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -13,7 +13,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING "ȝ ." !define PIDGIN_IS_RUNNING " . ." -!define GTK_INSTALLER_NEEDED " GTK+ .$\r ${GTK_MIN_VERSION} GTK+ " ; License Page !define PIDGIN_LICENSE_BUTTON " >" @@ -33,7 +32,6 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC " " ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT " GTK+ . Ͽ$\r: $(^Name) ." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE " " @@ -42,8 +40,6 @@ !define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL " . ." ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR " GTK+." -!define GTK_BAD_INSTALL_PATH " ." ; URL Handler section !define URI_HANDLERS_SECTION_TITLE " "
--- a/pidgin/win32/nsis/translations/polish.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/polish.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -8,8 +8,6 @@ ;; Version 2 ;; -; Startup GTK+ check -!define GTK_INSTALLER_NEEDED "Runtime rodowiska GTK+ zosta zagubiony lub wymaga upgrade-u.$\r Prosz zainstaluj v${GTK_MIN_VERSION} albo wysz wersj runtime-u GTK+." ; License Page !define PIDGIN_LICENSE_BUTTON "Dalej >" @@ -22,14 +20,11 @@ !define GTK_SECTION_DESCRIPTION "Wieloplatformowe narzdzie GUI, uywane w Pidgin" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Znaleziono star wersj runtime-u GTK+. Czy chcesz upgrade-owa?$\rNote: $(^Name) moe nie dziaa jeli nie wykonasz procedury." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Wejd na stron Pidgin Web Page" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Bd instalacji runtime-a GTK+." -!define GTK_BAD_INSTALL_PATH "Nie ma dostpu do wybranej cieki / aty." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "Deinstalator nie moe znale rejestrw dla Pidgin.$\r Wskazuje to na to, e instalacj przeprowadzi inny uytkownik."
--- a/pidgin/win32/nsis/translations/portuguese-br.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/portuguese-br.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -8,8 +8,6 @@ ;; Version 3 ;; -; Startup GTK+ check -!define GTK_INSTALLER_NEEDED "O ambiente de tempo de execuo do GTK+ est ausente ou precisa ser atualizado.$\rFavor instalar a verso v${GTK_MIN_VERSION} ou superior do ambiente de tempo de execuo do GTK+." ; License Page !define PIDGIN_LICENSE_BUTTON "Avanar >" @@ -29,11 +27,8 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "Crie uma entrada no Menu Iniciar para o Pidgin" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Uma verso antiga do ambiente de tempo de execuo do GTK+ foi encontrada. Voc deseja atualiz-lo?$\rNota: O $(^Name) poder no funcionar a menos que voc o faa." ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Erro ao instalar o ambiente de tempo de execuo do GTK+." -!define GTK_BAD_INSTALL_PATH "O caminho que voc digitou no pde ser acessado ou criado." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Visite a pgina da web do Pidgin para Windows"
--- a/pidgin/win32/nsis/translations/portuguese.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/portuguese.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -11,7 +11,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING "O instalador j est a ser executado." !define PIDGIN_IS_RUNNING "Uma instncia do Pidgin j est a ser executada. Saia do Pidgin e tente de novo." -!define GTK_INSTALLER_NEEDED "O ambiente de GTK+ est ausente ou precisa de ser actualizado.$\rPor favor instale a verso v${GTK_MIN_VERSION} ou mais recente do ambiente de GTK+." ; License Page !define PIDGIN_LICENSE_BUTTON "Seguinte >" @@ -31,14 +30,11 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "Criar uma entrada para o Pidgin na Barra de Iniciar" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Foi encontrada uma verso antiga do ambiente de execuo GTK+. Deseja actualiz-lo?$\rNota: O $(^Name) poder no funcionar se no o fizer." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Visite a Pgina Web do Pidgin para Windows" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Erro ao instalar o ambiente de execuo GTK+." -!define GTK_BAD_INSTALL_PATH "O caminho que digitou no pode ser acedido nem criado." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "O desinstalador no encontrou entradas de registo do Pidgin.$\r provvel que outro utilizador tenha instalado este programa."
--- a/pidgin/win32/nsis/translations/romanian.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/romanian.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -10,7 +10,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING "Instalarea este deja pornit." !define PIDGIN_IS_RUNNING "O instan a programului Pidgin este deja pornit. nchidei-o i ncercai din nou." -!define GTK_INSTALLER_NEEDED "Mediul GTK+ nu e prezent sau avei o versiune prea veche.$\rInstalai cel puin versiunea v${GTK_MIN_VERSION} a mediului GTK+" ; License Page !define PIDGIN_LICENSE_BUTTON "nainte >" @@ -30,14 +29,11 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "Creeaz o intrare Pidgin n meniul Start" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Avei o versiune veche a mediului GTK+. Dorii s o actualizai?$\rNot: E posibil ca $(^Name) s nu funcioneze cu versiunea veche." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Vizitai pagina de web Windows Pidgin" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Eroare la instalarea mediului GTK+." -!define GTK_BAD_INSTALL_PATH "Directorul specificat nu poate fi accesat sau creat." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "Programul de dezinstalare nu a gsit intrri Pidgin n regitri.$\rProbabil un alt utilizator a instalat aceast aplicaie."
--- a/pidgin/win32/nsis/translations/russian.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/russian.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -8,9 +8,6 @@ ;; Version 2 ;; -; Startup GTK+ check -!define GTK_INSTALLER_NEEDED " GTK+ .$\r v${GTK_MIN_VERSION} GTK+." - ; License Page !define PIDGIN_LICENSE_BUTTON " >" !define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) GPL. . $_CLICK" @@ -22,14 +19,11 @@ !define GTK_SECTION_DESCRIPTION " , Pidgin." ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT " GTK+. ?$\r: Pidgin ." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE " - Pidgin Windows." ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR " GTK+." -!define GTK_BAD_INSTALL_PATH " ." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 " Pidgin ..$\r ."
--- a/pidgin/win32/nsis/translations/serbian-latin.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/serbian-latin.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -7,8 +7,6 @@ ;; Author: Danilo Segan <dsegan@gmx.net> ;; -; Startup GTK+ check -!define GTK_INSTALLER_NEEDED "GTK+ okolina za izvravanje ili nije naena ili se moraunaprediti.$\rMolimo instalirajte v${GTK_MIN_VERSION} ili veu GTK+ okoline za izvravanje" ; Components Page !define PIDGIN_SECTION_TITLE "Pidgin klijent za brze poruke (neophodno)" @@ -17,11 +15,8 @@ !define GTK_SECTION_DESCRIPTION "Skup orua za grafiko okruenje, za vie platformi, koristi ga Pidgin " ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Naena je stara verzija GTK+ izvrne okoline. Da li elite da je unapredite?$\rPrimedba: Ukoliko to ne uradite, $(^Name) moda nee raditi." ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Greka prilikom instalacije GTK+ okoline za izvravanje." -!define GTK_BAD_INSTALL_PATH "Putanja koju ste naveli se ne moe ni napraviti niti joj se moe prii." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "Program za uklanjanje instalacije ne moe da pronae stavke registra za Pidgin.$\rVerovatno je ovu aplikaciju instalirao drugi korisnik."
--- a/pidgin/win32/nsis/translations/simp-chinese.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/simp-chinese.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -11,7 +11,6 @@ ; Startup GTK+ check !define INSTALLER_IS_RUNNING "װѾС" !define PIDGIN_IS_RUNNING "Pidgin ʵС˳ Pidgin ȻһΡ" -!define GTK_INSTALLER_NEEDED "ȱ GTK+ ʱ̻Ҫ¸û$\r밲װ v${GTK_MIN_VERSION} ߰汾 GTK+ ʱ̻" ; License Page !define PIDGIN_LICENSE_BUTTON "һ >" @@ -31,14 +30,11 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "ڿʼ˵д Pidgin Ŀݷʽ" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "˾ɰ汾 GTK+ ʱ̡Ҫ?$\rע: $(^Name) " ; Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE " Windows Pidgin ҳ" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "װ GTK+ ʱʧܡ" -!define GTK_BAD_INSTALL_PATH "ʻ·" ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "жسҲ Pidgin עĿ$\rûװ˴˳"
--- a/pidgin/win32/nsis/translations/slovak.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/slovak.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -11,7 +11,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING "Intalcia je u spusten" !define PIDGIN_IS_RUNNING "Pidgin je prve spusten. Vypnite ho a skste znova." -!define GTK_INSTALLER_NEEDED "GTK+ runtime prostredie chba alebo mus by upgradovan.$\rNaintalujte, prosm, GTK+ runtime verziu v${GTK_MIN_VERSION}, alebo noviu" ; License Page !define PIDGIN_LICENSE_BUTTON "alej >" @@ -31,14 +30,11 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "Vytvori odkaz na Pidgin v tart Menu" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Bola njden staria verzia GTK+ runtime. Prajete si upgradova sasn verziu?$\rPoznmka: $(^Name) nemus po upgradovan fungova sprvne." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Navtvi webstrnku Windows Pidgin" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Chyba pri intalcii GTK+ runtime." -!define GTK_BAD_INSTALL_PATH "Zadan cesta nie je prstupn alebo ju nie je mon vytvori." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "Intaltoru sa nepodarilo njs poloky v registri pre Pidgin.$\rJe mon, e tto aplikciu naintaloval in pouvate."
--- a/pidgin/win32/nsis/translations/slovenian.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/slovenian.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -11,7 +11,6 @@ ; Startup GTK+ check !define INSTALLER_IS_RUNNING "Nameanje e poteka." !define PIDGIN_IS_RUNNING "Trenutno e tee ena razliica Pidgina. Prosimo, zaprite aplikacijo in poskusite znova." -!define GTK_INSTALLER_NEEDED "Izvajalno okolje GTK+ manjka ali pa ga je potrebno nadgraditi.$\rProsimo, namestite v${GTK_MIN_VERSION} ali novejo razliico izvajalnega okolja GTK+" ; License Page !define PIDGIN_LICENSE_BUTTON "Naprej >" @@ -31,7 +30,6 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "Ustvari izbiro Pidgin v meniju Start" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Nameeno imate starejo razliico izvajalnega okolja GTK+. Jo elite nadgraditi?$\rOpomba: e je ne boste nadgradili, $(^Name) morda ne bo deloval." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Obiite spletno stran Windows Pidgin" @@ -40,8 +38,6 @@ !define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Trenutno nameene razliice Pidgina ni mogoe odstraniti. Nova razliica bo nameena brez odstranitve trenutno nameene razliice." ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Napaka pri namestitvi izvajalnega okolja GTK+." -!define GTK_BAD_INSTALL_PATH "Pot, ki ste jo vnesli, ni dosegljiva ali je ni mogoe ustvariti." ; URL Handler section !define URI_HANDLERS_SECTION_TITLE "URI Handlers"
--- a/pidgin/win32/nsis/translations/spanish.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/spanish.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -8,9 +8,6 @@ ;; Version 2 ;; -; Startup GTK+ check -!define GTK_INSTALLER_NEEDED "El entorno de ejecucin de GTK+ falta o necesita ser actualizado.$\rPor favor, instale la versin v${GTK_MIN_VERSION} del ejecutable GTK+ o alguna posterior." - ; License Page !define PIDGIN_LICENSE_BUTTON "Siguiente >" !define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) se distribuye bajo la licencia GPL. Esta licencia se incluye aqu slo con propsito informativo: $_CLICK" @@ -22,14 +19,11 @@ !define GTK_SECTION_DESCRIPTION "Una suite de herramientas GUI multiplataforma, utilizada por Pidgin" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Se ha encontrado una versin antiga del ejecutable de GTK+. Desea actualizarla?$\rObservacin: $(^Name) no funcionar a menos que lo haga." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Visite la pgina Web de Pidgin Windows" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Error al instalar el ejecutable GTK+." -!define GTK_BAD_INSTALL_PATH "No se pudo acceder o crear la ruta que vd. indic." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "El desinstalador no pudo encontrar las entradas en el registro de Pidgin.$\rEs probable que otro usuario instalara la aplicacin."
--- a/pidgin/win32/nsis/translations/swedish.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/swedish.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -1,80 +1,76 @@ -;; -;; swedish.nsh -;; -;; Swedish language strings for the Windows Pidgin NSIS installer. -;; Windows Code page: 1252 -;; -;; Author: Tore Lundqvist <tlt@mima.x.se>, 2003. -;; Author: Peter Hjalmarsson <xake@telia.com>, 2005. -;; Version 3 - -; Startup Checks -!define INSTALLER_IS_RUNNING "Installationsprogrammet krs redan." -!define PIDGIN_IS_RUNNING "En instans av Pidgin krs redan. Avsluta Pidgin och frsk igen." -!define GTK_INSTALLER_NEEDED "Krmiljn GTK+ r antingen inte installerat eller behver uppgraderas.$\rVar god installera v${GTK_MIN_VERSION} eller hgre av GTK+-krmiljn." - -; License Page -!define PIDGIN_LICENSE_BUTTON "Nsta >" -!define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) r utgivet under GPL. Licensen finns tillgnglig hr fr informationssyften enbart. $_CLICK" - -; Components Page -!define PIDGIN_SECTION_TITLE "Pidgin Snabbmeddelandeklient (obligatorisk)" -!define GTK_SECTION_TITLE "GTK+-krmilj (obligatorisk)" -!define PIDGIN_SHORTCUTS_SECTION_TITLE "Genvgar" -!define PIDGIN_DESKTOP_SHORTCUT_SECTION_TITLE "Skrivbord" -!define PIDGIN_STARTMENU_SHORTCUT_SECTION_TITLE "Startmeny" -!define PIDGIN_SECTION_DESCRIPTION "Pidgins krnfiler och DLL:er" -!define GTK_SECTION_DESCRIPTION "En GUI-verktygsuppsttning fr flera olika plattformar som Pidgin anvnder." - -!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION "Genvgar fr att starta Pidgin" -!define PIDGIN_DESKTOP_SHORTCUT_DESC "Skapar en genvg till Pidgin p skrivbordet" -!define PIDGIN_STARTMENU_SHORTCUT_DESC "Skapar ett tillgg i startmenyn fr Pidgin" - -; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "En ldre version av GTK+ runtime hittades, vill du uppgradera den?$\rOBS! $(^Name) kommer kanske inte att fungera om du inte uppgraderar." - -; Installer Finish Page -!define PIDGIN_FINISH_VISIT_WEB_SITE "Besk Windows-Pidgin hemsida" - -; Pidgin Section Prompts and Texts -!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Kunde inte avinstallera den nuvarande versionen av Pidgin. Den nya versionen kommer att installeras utan att ta bort den fr nrvarande installerade versionen." - -; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Fel vid installation av GTK+ runtime." -!define GTK_BAD_INSTALL_PATH "Den skvg du angivit gr inte att komma t eller skapa." - -; URL Handler section -!define URI_HANDLERS_SECTION_TITLE "URI Hanterare" - -; Uninstall Section Prompts -!define un.PIDGIN_UNINSTALL_ERROR_1 "Avinstalleraren kunde inte hitta registervrden fr Pidgin.$\rAntagligen har en annan anvndare installerat applikationen." -!define un.PIDGIN_UNINSTALL_ERROR_2 "Du har inte rttigheter att avinstallera den hr applikationen." - -; Spellcheck Section Prompts -!define PIDGIN_SPELLCHECK_SECTION_TITLE "Std fr rttstavning" -!define PIDGIN_SPELLCHECK_ERROR "Fel vid installation fr rttstavning" -!define PIDGIN_SPELLCHECK_DICT_ERROR "Fel vid installation av rttstavningsordlista" -!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION "Std fr Rttstavning. (Internetanslutning krvs fr installation)" -!define ASPELL_INSTALL_FAILED "Installationen misslyckades" -!define PIDGIN_SPELLCHECK_BRETON "Bretonska" -!define PIDGIN_SPELLCHECK_CATALAN "Katalanska" -!define PIDGIN_SPELLCHECK_CZECH "Tjeckiska" -!define PIDGIN_SPELLCHECK_WELSH "Kymriska" -!define PIDGIN_SPELLCHECK_DANISH "Danska" -!define PIDGIN_SPELLCHECK_GERMAN "Tyska" -!define PIDGIN_SPELLCHECK_GREEK "Grekiska" -!define PIDGIN_SPELLCHECK_ENGLISH "Engelska" -!define PIDGIN_SPELLCHECK_ESPERANTO "Esperanto" -!define PIDGIN_SPELLCHECK_SPANISH "Spanska" -!define PIDGIN_SPELLCHECK_FAROESE "Friska" -!define PIDGIN_SPELLCHECK_FRENCH "Franska" -!define PIDGIN_SPELLCHECK_ITALIAN "Italienska" -!define PIDGIN_SPELLCHECK_DUTCH "Nederlndska" -!define PIDGIN_SPELLCHECK_NORWEGIAN "Norska" -!define PIDGIN_SPELLCHECK_POLISH "Polska" -!define PIDGIN_SPELLCHECK_PORTUGUESE "Portugisiska" -!define PIDGIN_SPELLCHECK_ROMANIAN "Rumnska" -!define PIDGIN_SPELLCHECK_RUSSIAN "Ryska" -!define PIDGIN_SPELLCHECK_SLOVAK "Slovakiska" -!define PIDGIN_SPELLCHECK_SWEDISH "Svenska" -!define PIDGIN_SPELLCHECK_UKRAINIAN "Ukrainska" +;; +;; swedish.nsh +;; +;; Swedish language strings for the Windows Pidgin NSIS installer. +;; Windows Code page: 1252 +;; +;; Author: Tore Lundqvist <tlt@mima.x.se>, 2003. +;; Author: Peter Hjalmarsson <xake@telia.com>, 2005. +;; Version 3 + +; Startup Checks +!define INSTALLER_IS_RUNNING "Installationsprogrammet krs redan." +!define PIDGIN_IS_RUNNING "En instans av Pidgin krs redan. Avsluta Pidgin och frsk igen." + +; License Page +!define PIDGIN_LICENSE_BUTTON "Nsta >" +!define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) r utgivet under GPL. Licensen finns tillgnglig hr fr informationssyften enbart. $_CLICK" + +; Components Page +!define PIDGIN_SECTION_TITLE "Pidgin Snabbmeddelandeklient (obligatorisk)" +!define GTK_SECTION_TITLE "GTK+-krmilj (obligatorisk)" +!define PIDGIN_SHORTCUTS_SECTION_TITLE "Genvgar" +!define PIDGIN_DESKTOP_SHORTCUT_SECTION_TITLE "Skrivbord" +!define PIDGIN_STARTMENU_SHORTCUT_SECTION_TITLE "Startmeny" +!define PIDGIN_SECTION_DESCRIPTION "Pidgins krnfiler och DLL:er" +!define GTK_SECTION_DESCRIPTION "En GUI-verktygsuppsttning fr flera olika plattformar som Pidgin anvnder." + +!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION "Genvgar fr att starta Pidgin" +!define PIDGIN_DESKTOP_SHORTCUT_DESC "Skapar en genvg till Pidgin p skrivbordet" +!define PIDGIN_STARTMENU_SHORTCUT_DESC "Skapar ett tillgg i startmenyn fr Pidgin" + +; GTK+ Directory Page + +; Installer Finish Page +!define PIDGIN_FINISH_VISIT_WEB_SITE "Besk Windows-Pidgin hemsida" + +; Pidgin Section Prompts and Texts +!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Kunde inte avinstallera den nuvarande versionen av Pidgin. Den nya versionen kommer att installeras utan att ta bort den fr nrvarande installerade versionen." + +; GTK+ Section Prompts + +; URL Handler section +!define URI_HANDLERS_SECTION_TITLE "URI Hanterare" + +; Uninstall Section Prompts +!define un.PIDGIN_UNINSTALL_ERROR_1 "Avinstalleraren kunde inte hitta registervrden fr Pidgin.$\rAntagligen har en annan anvndare installerat applikationen." +!define un.PIDGIN_UNINSTALL_ERROR_2 "Du har inte rttigheter att avinstallera den hr applikationen." + +; Spellcheck Section Prompts +!define PIDGIN_SPELLCHECK_SECTION_TITLE "Std fr rttstavning" +!define PIDGIN_SPELLCHECK_ERROR "Fel vid installation fr rttstavning" +!define PIDGIN_SPELLCHECK_DICT_ERROR "Fel vid installation av rttstavningsordlista" +!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION "Std fr Rttstavning. (Internetanslutning krvs fr installation)" +!define ASPELL_INSTALL_FAILED "Installationen misslyckades" +!define PIDGIN_SPELLCHECK_BRETON "Bretonska" +!define PIDGIN_SPELLCHECK_CATALAN "Katalanska" +!define PIDGIN_SPELLCHECK_CZECH "Tjeckiska" +!define PIDGIN_SPELLCHECK_WELSH "Kymriska" +!define PIDGIN_SPELLCHECK_DANISH "Danska" +!define PIDGIN_SPELLCHECK_GERMAN "Tyska" +!define PIDGIN_SPELLCHECK_GREEK "Grekiska" +!define PIDGIN_SPELLCHECK_ENGLISH "Engelska" +!define PIDGIN_SPELLCHECK_ESPERANTO "Esperanto" +!define PIDGIN_SPELLCHECK_SPANISH "Spanska" +!define PIDGIN_SPELLCHECK_FAROESE "Friska" +!define PIDGIN_SPELLCHECK_FRENCH "Franska" +!define PIDGIN_SPELLCHECK_ITALIAN "Italienska" +!define PIDGIN_SPELLCHECK_DUTCH "Nederlndska" +!define PIDGIN_SPELLCHECK_NORWEGIAN "Norska" +!define PIDGIN_SPELLCHECK_POLISH "Polska" +!define PIDGIN_SPELLCHECK_PORTUGUESE "Portugisiska" +!define PIDGIN_SPELLCHECK_ROMANIAN "Rumnska" +!define PIDGIN_SPELLCHECK_RUSSIAN "Ryska" +!define PIDGIN_SPELLCHECK_SLOVAK "Slovakiska" +!define PIDGIN_SPELLCHECK_SWEDISH "Svenska" +!define PIDGIN_SPELLCHECK_UKRAINIAN "Ukrainska"
--- a/pidgin/win32/nsis/translations/trad-chinese.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/trad-chinese.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -13,7 +13,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING "w˵{b椤C" !define PIDGIN_IS_RUNNING "Pidgin b椤AХoӵ{AwˡC" -!define GTK_INSTALLER_NEEDED "䤣ŦX GTK+ ҩάOݭnQsC$\rЦw v${GTK_MIN_VERSION} ΥHW GTK+ ҡC" ; License Page !define PIDGIN_LICENSE_BUTTON "U@B >" @@ -33,7 +32,6 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "b}l\إ߱|" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "o{@ª GTK+ ҡCznNɯŶܡH$\rЪ`NGpGzɯšA $(^Name) iLkTQC" ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "X Windows Pidgin " @@ -42,8 +40,6 @@ !define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Lkثeww˪ PidginAsNbgªpUiwˡC" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "w GTK+ ҮɵoͿ~C" -!define GTK_BAD_INSTALL_PATH "zҿJw˥ؿLksΫإߡC" ; URL Handler section !define URI_HANDLERS_SECTION_TITLE "URI Bz{"
--- a/pidgin/win32/nsis/translations/valencian.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/valencian.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -14,7 +14,6 @@ ; Startup Checks !define INSTALLER_IS_RUNNING "L'instalador encara est eixecutant-se." !define PIDGIN_IS_RUNNING "Una instancia de Pidgin est eixecutant-se. Ix del Pidgin i torna a intentar-ho." -!define GTK_INSTALLER_NEEDED "L'entorn d'eixecucio GTK+ no es troba o necessita ser actualisat.$\rPer favor instala la versio${GTK_MIN_VERSION} o superior de l'entorn GTK+" ; License Page !define PIDGIN_LICENSE_BUTTON "Segent >" @@ -34,14 +33,11 @@ !define PIDGIN_STARTMENU_SHORTCUT_DESC "Crear una entrada per a Pidgin en Menu Inici" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Una versio antiua de l'entorn GTK+ fon trobada. Vols actualisar-la?$\rNota: $(^Name) no funcionar si no ho fas." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Visita la pagina de Pidgin per a Windows" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Erro instalant l'entorn GTK+." -!define GTK_BAD_INSTALL_PATH "La ruta introduida no pot ser accedida o creada." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "El desinstalador podria no trobar les entrades del registre de Pidgin.$\rProbablement un atre usuari instal esta aplicacio."
--- a/pidgin/win32/nsis/translations/vietnamese.nsh Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/nsis/translations/vietnamese.nsh Wed Nov 11 03:07:21 2009 +0000 @@ -9,9 +9,6 @@ ;; it does, these translations can not be used. ;; -; Startup GTK+ check -!define GTK_INSTALLER_NEEDED "The GTK+ runtime environment không có hoặc cần được nâng cấp.$\rHãy cài đặt GTK+ runtime v${GTK_MIN_VERSION} hoặc mới hơn" - ; License Page !define PIDGIN_LICENSE_BUTTON "Tiếp theo >" !define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) được phát hành theo giấy phép GPL. Giấy phép thấy ở đây chỉ là để cung cấp thông tin mà thôi. $_CLICK" @@ -23,14 +20,11 @@ !define GTK_SECTION_DESCRIPTION "Bộ công cụ giao diện đồ họa đa nền để dùng cho Pidgin" ; GTK+ Directory Page -!define GTK_UPGRADE_PROMPT "Phát hiện thấy có phiên bản cũ của GTK+ runtime. Bạn muốn nâng cấp không?$\rNote: $(^Name) có thể không chạy nếu không nâng cấp." ; Installer Finish Page !define PIDGIN_FINISH_VISIT_WEB_SITE "Hãy xem trang chủ Windows Pidgin" ; GTK+ Section Prompts -!define GTK_INSTALL_ERROR "Lỗi cài đặt GTK+ runtime." -!define GTK_BAD_INSTALL_PATH "Đường dẫn mà bạn nhập có thể không truy cập được hay không tạo được." ; Uninstall Section Prompts !define un.PIDGIN_UNINSTALL_ERROR_1 "Trình gỡ cài đặt không tìm được các registry entry cho Pidgin.$\rCó thể là chương trình được người dùng khác cài đặt."
--- a/pidgin/win32/winpidgin.c Mon Nov 02 23:14:33 2009 +0000 +++ b/pidgin/win32/winpidgin.c Wed Nov 11 03:07:21 2009 +0000 @@ -26,33 +26,23 @@ */ /* This is for ATTACH_PARENT_PROCESS */ +#define UNICODE +#define _UNICODE #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x501 #endif #include <windows.h> +#include <tchar.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <stdio.h> - -/* These will hopefully be in the win32api next time it is updated - at which point, we'll remove them */ -#ifndef LANG_PERSIAN -#define LANG_PERSIAN 0x29 -#endif -#ifndef LANG_BOSNIAN -#define SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_LATIN 0x05 -#define SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_CYRILLIC 0x08 -#endif -#ifndef SUBLANG_CROATIAN_BOSNIA_HERZEGOVINA_LATIN -#define SUBLANG_CROATIAN_BOSNIA_HERZEGOVINA_LATIN 0x04 -#endif -#ifndef LANG_XHOSA -#define LANG_XHOSA 0x34 -#endif - +#include <sys/types.h> +#include <sys/stat.h> +#include "config.h" typedef int (CALLBACK* LPFNPIDGINMAIN)(HINSTANCE, int, char**); -typedef void (CALLBACK* LPFNSETDLLDIRECTORY)(LPCTSTR); +typedef void (CALLBACK* LPFNSETDLLDIRECTORY)(LPCWSTR); typedef BOOL (CALLBACK* LPFNATTACHCONSOLE)(DWORD); static BOOL portable_mode = FALSE; @@ -63,19 +53,19 @@ static LPFNPIDGINMAIN pidgin_main = NULL; static LPFNSETDLLDIRECTORY MySetDllDirectory = NULL; -static const char *get_win32_error_message(DWORD err) { - static char err_msg[512]; +static const TCHAR *get_win32_error_message(DWORD err) { + static TCHAR err_msg[512]; FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR) &err_msg, sizeof(err_msg), NULL); + (LPTSTR) &err_msg, sizeof(err_msg) / sizeof(TCHAR), NULL); return err_msg; } -static BOOL read_reg_string(HKEY key, char* sub_key, char* val_name, LPBYTE data, LPDWORD data_len) { +static BOOL read_reg_string(HKEY key, TCHAR *sub_key, TCHAR *val_name, LPBYTE data, LPDWORD data_len) { HKEY hkey; BOOL ret = FALSE; LONG retv; @@ -86,11 +76,11 @@ NULL, NULL, data, data_len))) ret = TRUE; else { - const char *err_msg = get_win32_error_message(retv); + const TCHAR *err_msg = get_win32_error_message(retv); - printf("Could not read reg key '%s' subkey '%s' value: '%s'.\nMessage: (%ld) %s\n", - (key == HKEY_LOCAL_MACHINE) ? "HKLM" - : ((key == HKEY_CURRENT_USER) ? "HKCU" : "???"), + _tprintf(_T("Could not read reg key '%s' subkey '%s' value: '%s'.\nMessage: (%ld) %s\n"), + (key == HKEY_LOCAL_MACHINE) ? _T("HKLM") + : ((key == HKEY_CURRENT_USER) ? _T("HKCU") : _T("???")), sub_key, val_name, retv, err_msg); } RegCloseKey(hkey); @@ -99,23 +89,36 @@ TCHAR szBuf[80]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, retv, 0, - (LPTSTR) &szBuf, sizeof(szBuf), NULL); - printf("Could not open reg subkey: %s\nError: (%ld) %s\n", + (LPTSTR) &szBuf, sizeof(szBuf) / sizeof(TCHAR), NULL); + _tprintf(_T("Could not open reg subkey: %s\nError: (%ld) %s\n"), sub_key, retv, szBuf); } return ret; } -static void common_dll_prep(const char *path) { +static void common_dll_prep(const TCHAR *path) { HMODULE hmod; HKEY hkey; + struct _stat stat_buf; + TCHAR test_path[MAX_PATH + 1]; - printf("GTK+ path found: %s\n", path); + _sntprintf(test_path, sizeof(test_path) / sizeof(TCHAR), + _T("%s\\libgtk-win32-2.0-0.dll"), path); + test_path[sizeof(test_path) / sizeof(TCHAR) - 1] = _T('\0'); - if ((hmod = GetModuleHandle("kernel32.dll"))) { + if (_tstat(test_path, &stat_buf) != 0) { + printf("Unable to determine GTK+ path. \n" + "Assuming GTK+ is in the PATH.\n"); + return; + } + + + _tprintf(_T("GTK+ path found: %s\n"), path); + + if ((hmod = GetModuleHandle(_T("kernel32.dll")))) { MySetDllDirectory = (LPFNSETDLLDIRECTORY) GetProcAddress( - hmod, "SetDllDirectoryA"); + hmod, "SetDllDirectoryW"); if (!MySetDllDirectory) printf("SetDllDirectory not supported\n"); } else @@ -141,20 +144,21 @@ */ osinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&osinfo); - if ((osinfo.dwMajorVersion == 5 && - osinfo.dwMinorVersion == 0 && - strcmp(osinfo.szCSDVersion, "Service Pack 3") >= 0) || - (osinfo.dwMajorVersion == 5 && - osinfo.dwMinorVersion == 1 && - strcmp(osinfo.szCSDVersion, "") >= 0) + if ((osinfo.dwMajorVersion == 5 + && osinfo.dwMinorVersion == 0 + && _tcscmp(osinfo.szCSDVersion, _T("Service Pack 3")) >= 0) + || + (osinfo.dwMajorVersion == 5 + && osinfo.dwMinorVersion == 1 + && _tcscmp(osinfo.szCSDVersion, _T("")) >= 0) ) { DWORD regval = 1; DWORD reglen = sizeof(DWORD); printf("Using Win2k (SP3+) / WinXP (No SP)... Checking SafeDllSearch\n"); read_reg_string(HKEY_LOCAL_MACHINE, - "System\\CurrentControlSet\\Control\\Session Manager", - "SafeDllSearchMode", + _T("System\\CurrentControlSet\\Control\\Session Manager"), + _T("SafeDllSearchMode"), (LPBYTE) ®val, ®len); @@ -162,16 +166,16 @@ printf("Trying to set SafeDllSearchMode to 0\n"); regval = 0; if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, - "System\\CurrentControlSet\\Control\\Session Manager", + _T("System\\CurrentControlSet\\Control\\Session Manager"), 0, KEY_SET_VALUE, &hkey ) == ERROR_SUCCESS) { if (RegSetValueEx(hkey, - "SafeDllSearchMode", 0, + _T("SafeDllSearchMode"), 0, REG_DWORD, (LPBYTE) ®val, sizeof(DWORD) ) != ERROR_SUCCESS) printf("Error writing SafeDllSearchMode. Error: %u\n", - (UINT) GetLastError()); + (UINT) GetLastError()); RegCloseKey(hkey); } else printf("Error opening Session Manager key for writing. Error: %u\n", @@ -182,29 +186,23 @@ } } -static void portable_mode_dll_prep(const char *pidgin_dir) { +static void portable_mode_dll_prep(const TCHAR *pidgin_dir) { /* need to be able to fit MAX_PATH + "PIDGIN_ASPELL_DIR=\\Aspell\\bin" in path2 */ - char path[MAX_PATH + 1]; - char path2[MAX_PATH + 33]; - const char *prev = NULL; + TCHAR path[MAX_PATH + 1]; + TCHAR path2[MAX_PATH + 33]; + const TCHAR *prev = NULL; /* We assume that GTK+ is installed under \\path\to\Pidgin\..\GTK * First we find \\path\to */ - if (*pidgin_dir) { + if (*pidgin_dir) /* pidgin_dir points to \\path\to\Pidgin */ - const char *tmp = pidgin_dir; - - while ((tmp = strchr(tmp, '\\'))) { - prev = tmp; - tmp++; - } - } + prev = _tcsrchr(pidgin_dir, _T('\\')); if (prev) { int cnt = (prev - pidgin_dir); - strncpy(path, pidgin_dir, cnt); - path[cnt] = '\0'; + _tcsncpy(path, pidgin_dir, cnt); + path[cnt] = _T('\0'); } else { printf("Unable to determine current executable path. \n" "This will prevent the settings dir from being set.\n" @@ -213,160 +211,141 @@ } /* Set $HOME so that the GTK+ settings get stored in the right place */ - _snprintf(path2, sizeof(path2), "HOME=%s", path); - _putenv(path2); + _sntprintf(path2, sizeof(path2) / sizeof(TCHAR), _T("HOME=%s"), path); + _tputenv(path2); /* Set up the settings dir base to be \\path\to * The actual settings dir will be \\path\to\.purple */ - _snprintf(path2, sizeof(path2), "PURPLEHOME=%s", path); - printf("Setting settings dir: %s\n", path2); - _putenv(path2); + _sntprintf(path2, sizeof(path2) / sizeof(TCHAR), _T("PURPLEHOME=%s"), path); + _tprintf(_T("Setting settings dir: %s\n"), path2); + _tputenv(path2); - _snprintf(path2, sizeof(path2), "PIDGIN_ASPELL_DIR=%s\\Aspell\\bin", path); - printf("%s\n", path2); - _putenv(path2); + _sntprintf(path2, sizeof(path2) / sizeof(TCHAR), _T("PIDGIN_ASPELL_DIR=%s\\Aspell\\bin"), path); + _tprintf(_T("%s\n"), path2); + _tputenv(path2); /* set the GTK+ path to be \\path\to\GTK\bin */ - strcat(path, "\\GTK\\bin"); + _tcscat(path, _T("\\GTK\\bin")); common_dll_prep(path); } -static void dll_prep() { - char path[MAX_PATH + 1]; - HKEY hkey; - char gtkpath[MAX_PATH + 1]; - DWORD plen; +static void dll_prep(const TCHAR *pidgin_dir) { + TCHAR gtk_path[MAX_PATH + 1]; + gtk_path[0] = _T('\0'); - plen = sizeof(gtkpath); - hkey = HKEY_CURRENT_USER; - if (!read_reg_string(hkey, "SOFTWARE\\GTK\\2.0", "Path", - (LPBYTE) >kpath, &plen)) { - hkey = HKEY_LOCAL_MACHINE; - if (!read_reg_string(hkey, "SOFTWARE\\GTK\\2.0", "Path", - (LPBYTE) >kpath, &plen)) { - printf("GTK+ Path Registry Key not found. " - "Assuming GTK+ is in the PATH.\n"); - return; - } + if (*pidgin_dir) { + _sntprintf(gtk_path, sizeof(gtk_path) / sizeof(TCHAR), _T("%s\\Gtk\\bin"), pidgin_dir); + gtk_path[sizeof(gtk_path) / sizeof(TCHAR)] = _T('\0'); } - /* this value is replaced during a successful RegQueryValueEx() */ - plen = sizeof(path); - /* Determine GTK+ dll path .. */ - if (!read_reg_string(hkey, "SOFTWARE\\GTK\\2.0", "DllPath", - (LPBYTE) &path, &plen)) { - strcpy(path, gtkpath); - strcat(path, "\\bin"); - } - - common_dll_prep(path); + common_dll_prep(gtk_path); } -static char* winpidgin_lcid_to_posix(LCID lcid) { - char *posix = NULL; +static TCHAR* winpidgin_lcid_to_posix(LCID lcid) { + TCHAR *posix = NULL; int lang_id = PRIMARYLANGID(lcid); int sub_id = SUBLANGID(lcid); switch (lang_id) { - case LANG_AFRIKAANS: posix = "af"; break; - case LANG_ARABIC: posix = "ar"; break; - case LANG_AZERI: posix = "az"; break; - case LANG_BENGALI: posix = "bn"; break; - case LANG_BULGARIAN: posix = "bg"; break; - case LANG_CATALAN: posix = "ca"; break; - case LANG_CZECH: posix = "cs"; break; - case LANG_DANISH: posix = "da"; break; - case LANG_ESTONIAN: posix = "et"; break; - case LANG_PERSIAN: posix = "fa"; break; - case LANG_GERMAN: posix = "de"; break; - case LANG_GREEK: posix = "el"; break; + case LANG_AFRIKAANS: posix = _T("af"); break; + case LANG_ARABIC: posix = _T("ar"); break; + case LANG_AZERI: posix = _T("az"); break; + case LANG_BENGALI: posix = _T("bn"); break; + case LANG_BULGARIAN: posix = _T("bg"); break; + case LANG_CATALAN: posix = _T("ca"); break; + case LANG_CZECH: posix = _T("cs"); break; + case LANG_DANISH: posix = _T("da"); break; + case LANG_ESTONIAN: posix = _T("et"); break; + case LANG_PERSIAN: posix = _T("fa"); break; + case LANG_GERMAN: posix = _T("de"); break; + case LANG_GREEK: posix = _T("el"); break; case LANG_ENGLISH: switch (sub_id) { case SUBLANG_ENGLISH_UK: - posix = "en_GB"; break; + posix = _T("en_GB"); break; case SUBLANG_ENGLISH_AUS: - posix = "en_AU"; break; + posix = _T("en_AU"); break; case SUBLANG_ENGLISH_CAN: - posix = "en_CA"; break; + posix = _T("en_CA"); break; default: - posix = "en"; break; + posix = _T("en"); break; } break; - case LANG_SPANISH: posix = "es"; break; - case LANG_BASQUE: posix = "eu"; break; - case LANG_FINNISH: posix = "fi"; break; - case LANG_FRENCH: posix = "fr"; break; - case LANG_GALICIAN: posix = "gl"; break; - case LANG_GUJARATI: posix = "gu"; break; - case LANG_HEBREW: posix = "he"; break; - case LANG_HINDI: posix = "hi"; break; - case LANG_HUNGARIAN: posix = "hu"; break; + case LANG_SPANISH: posix = _T("es"); break; + case LANG_BASQUE: posix = _T("eu"); break; + case LANG_FINNISH: posix = _T("fi"); break; + case LANG_FRENCH: posix = _T("fr"); break; + case LANG_GALICIAN: posix = _T("gl"); break; + case LANG_GUJARATI: posix = _T("gu"); break; + case LANG_HEBREW: posix = _T("he"); break; + case LANG_HINDI: posix = _T("hi"); break; + case LANG_HUNGARIAN: posix = _T("hu"); break; case LANG_ICELANDIC: break; - case LANG_INDONESIAN: posix = "id"; break; - case LANG_ITALIAN: posix = "it"; break; - case LANG_JAPANESE: posix = "ja"; break; - case LANG_GEORGIAN: posix = "ka"; break; - case LANG_KANNADA: posix = "kn"; break; - case LANG_KOREAN: posix = "ko"; break; - case LANG_LITHUANIAN: posix = "lt"; break; - case LANG_MACEDONIAN: posix = "mk"; break; - case LANG_DUTCH: posix = "nl"; break; - case LANG_NEPALI: posix = "ne"; break; + case LANG_INDONESIAN: posix = _T("id"); break; + case LANG_ITALIAN: posix = _T("it"); break; + case LANG_JAPANESE: posix = _T("ja"); break; + case LANG_GEORGIAN: posix = _T("ka"); break; + case LANG_KANNADA: posix = _T("kn"); break; + case LANG_KOREAN: posix = _T("ko"); break; + case LANG_LITHUANIAN: posix = _T("lt"); break; + case LANG_MACEDONIAN: posix = _T("mk"); break; + case LANG_DUTCH: posix = _T("nl"); break; + case LANG_NEPALI: posix = _T("ne"); break; case LANG_NORWEGIAN: switch (sub_id) { case SUBLANG_NORWEGIAN_BOKMAL: - posix = "nb"; break; + posix = _T("nb"); break; case SUBLANG_NORWEGIAN_NYNORSK: - posix = "nn"; break; + posix = _T("nn"); break; } break; - case LANG_PUNJABI: posix = "pa"; break; - case LANG_POLISH: posix = "pl"; break; - case LANG_PASHTO: posix = "ps"; break; + case LANG_PUNJABI: posix = _T("pa"); break; + case LANG_POLISH: posix = _T("pl"); break; + case LANG_PASHTO: posix = _T("ps"); break; case LANG_PORTUGUESE: switch (sub_id) { case SUBLANG_PORTUGUESE_BRAZILIAN: - posix = "pt_BR"; break; + posix = _T("pt_BR"); break; default: - posix = "pt"; break; + posix = _T("pt"); break; } break; - case LANG_ROMANIAN: posix = "ro"; break; - case LANG_RUSSIAN: posix = "ru"; break; - case LANG_SLOVAK: posix = "sk"; break; - case LANG_SLOVENIAN: posix = "sl"; break; - case LANG_ALBANIAN: posix = "sq"; break; + case LANG_ROMANIAN: posix = _T("ro"); break; + case LANG_RUSSIAN: posix = _T("ru"); break; + case LANG_SLOVAK: posix = _T("sk"); break; + case LANG_SLOVENIAN: posix = _T("sl"); break; + case LANG_ALBANIAN: posix = _T("sq"); break; /* LANG_CROATIAN == LANG_SERBIAN == LANG_BOSNIAN */ case LANG_SERBIAN: switch (sub_id) { case SUBLANG_SERBIAN_LATIN: - posix = "sr@Latn"; break; + posix = _T("sr@Latn"); break; case SUBLANG_SERBIAN_CYRILLIC: - posix = "sr"; break; + posix = _T("sr"); break; case SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_CYRILLIC: case SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_LATIN: - posix = "bs"; break; + posix = _T("bs"); break; case SUBLANG_CROATIAN_BOSNIA_HERZEGOVINA_LATIN: - posix = "hr"; break; + posix = _T("hr"); break; } break; - case LANG_SWEDISH: posix = "sv"; break; - case LANG_TAMIL: posix = "ta"; break; - case LANG_TELUGU: posix = "te"; break; - case LANG_THAI: posix = "th"; break; - case LANG_TURKISH: posix = "tr"; break; - case LANG_UKRAINIAN: posix = "uk"; break; - case LANG_VIETNAMESE: posix = "vi"; break; - case LANG_XHOSA: posix = "xh"; break; + case LANG_SWEDISH: posix = _T("sv"); break; + case LANG_TAMIL: posix = _T("ta"); break; + case LANG_TELUGU: posix = _T("te"); break; + case LANG_THAI: posix = _T("th"); break; + case LANG_TURKISH: posix = _T("tr"); break; + case LANG_UKRAINIAN: posix = _T("uk"); break; + case LANG_VIETNAMESE: posix = _T("vi"); break; + case LANG_XHOSA: posix = _T("xh"); break; case LANG_CHINESE: switch (sub_id) { case SUBLANG_CHINESE_SIMPLIFIED: - posix = "zh_CN"; break; + posix = _T("zh_CN"); break; case SUBLANG_CHINESE_TRADITIONAL: - posix = "zh_TW"; break; + posix = _T("zh_TW"); break; default: - posix = "zh"; break; + posix = _T("zh"); break; } break; case LANG_URDU: break; @@ -397,8 +376,8 @@ /* Deal with exceptions */ if (posix == NULL) { switch (lcid) { - case 0x0455: posix = "my_MM"; break; /* Myanmar (Burmese) */ - case 9999: posix = "ku"; break; /* Kurdish (from NSIS) */ + case 0x0455: posix = _T("my_MM"); break; /* Myanmar (Burmese) */ + case 9999: posix = _T("ku"); break; /* Kurdish (from NSIS) */ } } @@ -410,19 +389,19 @@ - Check NSIS Installer Language reg value - Use default user locale */ -static const char *winpidgin_get_locale() { - const char *locale = NULL; +static const TCHAR *winpidgin_get_locale() { + const TCHAR *locale = NULL; LCID lcid; - char data[10]; - DWORD datalen = 10; + TCHAR data[10]; + DWORD datalen = sizeof(data) / sizeof(TCHAR); /* Check if user set PIDGINLANG env var */ - if ((locale = getenv("PIDGINLANG"))) + if ((locale = _tgetenv(_T("PIDGINLANG")))) return locale; - if (!portable_mode && read_reg_string(HKEY_CURRENT_USER, "SOFTWARE\\pidgin", - "Installer Language", (LPBYTE) &data, &datalen)) { - if ((locale = winpidgin_lcid_to_posix(atoi(data)))) + if (!portable_mode && read_reg_string(HKEY_CURRENT_USER, _T("SOFTWARE\\pidgin"), + _T("Installer Language"), (LPBYTE) &data, &datalen)) { + if ((locale = winpidgin_lcid_to_posix(_ttoi(data)))) return locale; } @@ -430,39 +409,39 @@ if ((locale = winpidgin_lcid_to_posix(lcid))) return locale; - return "en"; + return _T("en"); } static void winpidgin_set_locale() { - const char *locale = NULL; - char envstr[25]; + const TCHAR *locale; + TCHAR envstr[25]; locale = winpidgin_get_locale(); - _snprintf(envstr, 25, "LANG=%s", locale); - printf("Setting locale: %s\n", envstr); - _putenv(envstr); + _sntprintf(envstr, sizeof(envstr) / sizeof(TCHAR), _T("LANG=%s"), locale); + _tprintf(_T("Setting locale: %s\n"), envstr); + _tputenv(envstr); } static void winpidgin_add_stuff_to_path() { - char perl_path[MAX_PATH + 1]; - char *ppath = NULL; - char mit_kerberos_path[MAX_PATH + 1]; - char *mpath = NULL; + TCHAR perl_path[MAX_PATH + 1]; + TCHAR *ppath = NULL; + TCHAR mit_kerberos_path[MAX_PATH + 1]; + TCHAR *mpath = NULL; DWORD plen; printf("%s", "Looking for Perl... "); - plen = sizeof(perl_path); - if (read_reg_string(HKEY_LOCAL_MACHINE, "SOFTWARE\\Perl", "", + plen = sizeof(perl_path) / sizeof(TCHAR); + if (read_reg_string(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Perl"), _T(""), (LPBYTE) &perl_path, &plen)) { /* We *could* check for perl510.dll, but it seems unnecessary. */ - printf("found in '%s'.\n", perl_path); + _tprintf(_T("found in '%s'.\n"), perl_path); - if (perl_path[strlen(perl_path) - 1] != '\\') - strcat(perl_path, "\\"); - strcat(perl_path, "bin"); + if (perl_path[_tcslen(perl_path) - 1] != _T('\\')) + _tcscat(perl_path, _T("\\")); + _tcscat(perl_path, _T("bin")); ppath = perl_path; } else @@ -470,48 +449,47 @@ printf("%s", "Looking for MIT Kerberos... "); - plen = sizeof(mit_kerberos_path); - if (read_reg_string(HKEY_LOCAL_MACHINE, "SOFTWARE\\MIT\\Kerberos", "InstallDir", + plen = sizeof(mit_kerberos_path) / sizeof(TCHAR); + if (read_reg_string(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\MIT\\Kerberos"), _T("InstallDir"), (LPBYTE) &mit_kerberos_path, &plen)) { /* We *could* check for gssapi32.dll */ - printf("found in '%s'.\n", mit_kerberos_path); + _tprintf(_T("found in '%s'.\n"), mit_kerberos_path); - if (mit_kerberos_path[strlen(mit_kerberos_path) - 1] != '\\') - strcat(mit_kerberos_path, "\\"); - strcat(mit_kerberos_path, "bin"); + if (mit_kerberos_path[_tcslen(mit_kerberos_path) - 1] != _T('\\')) + _tcscat(mit_kerberos_path, _T("\\")); + _tcscat(mit_kerberos_path, _T("bin")); mpath = mit_kerberos_path; } else printf("%s", "not found.\n"); if (ppath != NULL || mpath != NULL) { - const char *path = getenv("PATH"); - BOOL add_ppath = ppath != NULL && (path == NULL || !strstr(path, ppath)); - BOOL add_mpath = mpath != NULL && (path == NULL || !strstr(path, mpath)); - char *newpath; + const TCHAR *path = _tgetenv(_T("PATH")); + BOOL add_ppath = ppath != NULL && (path == NULL || !_tcsstr(path, ppath)); + BOOL add_mpath = mpath != NULL && (path == NULL || !_tcsstr(path, mpath)); + TCHAR *newpath; int newlen; if (add_ppath || add_mpath) { /* Enough to add "PATH=" + path + ";" + ppath + ";" + mpath + \0 */ - newlen = 6 + (path ? strlen(path) + 1 : 0); + newlen = 6 + (path ? _tcslen(path) + 1 : 0); if (add_ppath) - newlen += strlen(ppath) + 1; + newlen += _tcslen(ppath) + 1; if (add_mpath) - newlen += strlen(mpath) + 1; - newpath = malloc(newlen); - *newpath = '\0'; + newlen += _tcslen(mpath) + 1; + newpath = malloc(newlen * sizeof(TCHAR)); - _snprintf(newpath, newlen, "PATH=%s%s%s%s%s%s", - path ? path : "", - path ? ";" : "", - add_ppath ? ppath : "", - add_ppath ? ";" : "", - add_mpath ? mpath : "", - add_mpath ? ";" : ""); + _sntprintf(newpath, newlen, _T("PATH=%s%s%s%s%s%s"), + path ? path : _T(""), + path ? _T(";") : _T(""), + add_ppath ? ppath : _T(""), + add_ppath ? _T(";") : _T(""), + add_mpath ? mpath : _T(""), + add_mpath ? _T(";") : _T("")); - printf("New PATH: %s\n", newpath); + _tprintf(_T("New PATH: %s\n"), newpath); - _putenv(newpath); + _tputenv(newpath); free(newpath); } } @@ -523,7 +501,7 @@ static BOOL winpidgin_set_running(BOOL fail_if_running) { HANDLE h; - if ((h = CreateMutex(NULL, FALSE, "pidgin_is_running"))) { + if ((h = CreateMutex(NULL, FALSE, _T("pidgin_is_running")))) { DWORD err = GetLastError(); if (err == ERROR_ALREADY_EXISTS) { if (fail_if_running) { @@ -531,14 +509,14 @@ printf("An instance of Pidgin is already running.\n"); - if((msg_win = FindWindowEx(NULL, NULL, TEXT("WinpidginMsgWinCls"), NULL))) + if((msg_win = FindWindowEx(NULL, NULL, _T("WinpidginMsgWinCls"), NULL))) if(SendMessage(msg_win, PIDGIN_WM_FOCUS_REQUEST, (WPARAM) NULL, (LPARAM) NULL)) return FALSE; /* If we get here, the focus request wasn't successful */ MessageBox(NULL, - "An instance of Pidgin is already running", + _T("An instance of Pidgin is already running"), NULL, MB_OK | MB_TOPMOST); return FALSE; @@ -549,80 +527,98 @@ return TRUE; } -#define PROTO_HANDLER_SWITCH "--protocolhandler=" +#define PROTO_HANDLER_SWITCH L"--protocolhandler=" -static void handle_protocol(char *cmd) { - char *remote_msg, *tmp1, *tmp2; - int len; +static void handle_protocol(wchar_t *cmd) { + char *remote_msg, *utf8msg; + wchar_t *tmp1, *tmp2; + int len, wlen; SIZE_T len_written; HWND msg_win; DWORD pid; HANDLE process; /* The start of the message */ - tmp1 = cmd + strlen(PROTO_HANDLER_SWITCH); + tmp1 = cmd + wcslen(PROTO_HANDLER_SWITCH); /* The end of the message */ - if ((tmp2 = strchr(tmp1, ' '))) - len = (tmp2 - tmp1); + if ((tmp2 = wcschr(tmp1, L' '))) + wlen = (tmp2 - tmp1); else - len = strlen(tmp1); + wlen = wcslen(tmp1); - if (len == 0) { + if (wlen == 0) { printf("No protocol message specified.\n"); return; } - if (!(msg_win = FindWindowEx(NULL, NULL, TEXT("WinpidginMsgWinCls"), NULL))) { + if (!(msg_win = FindWindowEx(NULL, NULL, _T("WinpidginMsgWinCls"), NULL))) { printf("Unable to find an instance of Pidgin to handle protocol message.\n"); return; } + len = WideCharToMultiByte(CP_UTF8, 0, tmp1, + wlen, NULL, 0, NULL, NULL); + if (len) { + utf8msg = malloc(len * sizeof(char)); + len = WideCharToMultiByte(CP_UTF8, 0, tmp1, + wlen, utf8msg, len, NULL, NULL); + } + + if (len == 0) { + printf("No protocol message specified.\n"); + return; + } + GetWindowThreadProcessId(msg_win, &pid); if (!(process = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, pid))) { DWORD dw = GetLastError(); - const char *err_msg = get_win32_error_message(dw); - printf("Unable to open Pidgin process. (%u) %s\n", (UINT) dw, err_msg); + const TCHAR *err_msg = get_win32_error_message(dw); + _tprintf(_T("Unable to open Pidgin process. (%u) %s\n"), (UINT) dw, err_msg); return; } - printf("Trying to handle protocol message:\n'%.*s'\n", len, tmp1); + wprintf(L"Trying to handle protocol message:\n'%.*s'\n", wlen, tmp1); - /* MEM_COMMIT initializes the memory to zero, - * so we don't need to worry that our section of tmp1 isn't nul-terminated */ + /* MEM_COMMIT initializes the memory to zero + * so we don't need to worry that our section of utf8msg isn't nul-terminated */ if ((remote_msg = (char*) VirtualAllocEx(process, NULL, len + 1, MEM_COMMIT, PAGE_READWRITE))) { - if (WriteProcessMemory(process, remote_msg, tmp1, len, &len_written)) { - if (!SendMessage(msg_win, PIDGIN_WM_PROTOCOL_HANDLE, len_written, (LPARAM) remote_msg)) + if (WriteProcessMemory(process, remote_msg, utf8msg, len, &len_written)) { + if (!SendMessageA(msg_win, PIDGIN_WM_PROTOCOL_HANDLE, len_written, (LPARAM) remote_msg)) printf("Unable to send protocol message to Pidgin instance.\n"); } else { DWORD dw = GetLastError(); - const char *err_msg = get_win32_error_message(dw); - printf("Unable to write to remote memory. (%u) %s\n", (UINT) dw, err_msg); + const TCHAR *err_msg = get_win32_error_message(dw); + _tprintf(_T("Unable to write to remote memory. (%u) %s\n"), (UINT) dw, err_msg); } VirtualFreeEx(process, remote_msg, 0, MEM_RELEASE); } else { DWORD dw = GetLastError(); - const char *err_msg = get_win32_error_message(dw); - printf("Unable to allocate remote memory. (%u) %s\n", (UINT) dw, err_msg); + const TCHAR *err_msg = get_win32_error_message(dw); + _tprintf(_T("Unable to allocate remote memory. (%u) %s\n"), (UINT) dw, err_msg); } CloseHandle(process); + free(utf8msg); } int _stdcall WinMain (struct HINSTANCE__ *hInstance, struct HINSTANCE__ *hPrevInstance, char *lpszCmdLine, int nCmdShow) { - char errbuf[512]; - char pidgin_dir[MAX_PATH]; - char exe_name[MAX_PATH]; + TCHAR errbuf[512]; + TCHAR pidgin_dir[MAX_PATH]; + TCHAR exe_name[MAX_PATH]; HMODULE hmod; - char *tmp; - int pidgin_argc = __argc; - char **pidgin_argv = __argv; - int i; - BOOL debug = FALSE, help = FALSE, version = FALSE, multiple = FALSE; + TCHAR *tmp; + wchar_t *wtmp; + int pidgin_argc; + char **pidgin_argv; /* This is in utf-8 */ + int i, j, k; + BOOL debug = FALSE, help = FALSE, version = FALSE, multiple = FALSE, success; + LPWSTR *szArglist; + LPWSTR cmdLine; /* If debug or help or version flag used, create console for output */ for (i = 1; i < __argc; i++) { @@ -655,7 +651,7 @@ * (_istty() doesn't work for stuff using the GUI subsystem) */ if (_fileno(stdout) == -1 || _fileno(stdout) == -2) { LPFNATTACHCONSOLE MyAttachConsole = NULL; - if ((hmod = GetModuleHandle("kernel32.dll"))) { + if ((hmod = GetModuleHandle(_T("kernel32.dll")))) { MyAttachConsole = (LPFNATTACHCONSOLE) GetProcAddress(hmod, "AttachConsole"); @@ -668,68 +664,104 @@ } } + cmdLine = GetCommandLineW(); + /* If this is a protocol handler invocation, deal with it accordingly */ - if ((tmp = strstr(lpszCmdLine, PROTO_HANDLER_SWITCH)) != NULL) { - handle_protocol(tmp); + if ((wtmp = wcsstr(cmdLine, PROTO_HANDLER_SWITCH)) != NULL) { + handle_protocol(wtmp); return 0; } /* Load exception handler if we have it */ if (GetModuleFileName(NULL, pidgin_dir, MAX_PATH) != 0) { - char *prev = NULL; - tmp = pidgin_dir; /* primitive dirname() */ - while ((tmp = strchr(tmp, '\\'))) { - prev = tmp; - tmp++; - } + tmp = _tcsrchr(pidgin_dir, _T('\\')); - if (prev) { - prev[0] = '\0'; + if (tmp) { + HMODULE hmod; + tmp[0] = _T('\0'); + + /* tmp++ will now point to the executable file name */ + _tcscpy(exe_name, tmp + 1); - /* prev++ will now point to the executable file name */ - strcpy(exe_name, prev + 1); + _tcscat(pidgin_dir, _T("\\exchndl.dll")); + if ((hmod = LoadLibrary(pidgin_dir))) { + FARPROC proc; + /* exchndl.dll is built without UNICODE */ + char debug_dir[MAX_PATH]; + printf("Loaded exchndl.dll\n"); + /* Temporarily override exchndl.dll's logfile + * to something sane (Pidgin will override it + * again when it initializes) */ + proc = GetProcAddress(hmod, "SetLogFile"); + if (proc) { + if (GetTempPathA(sizeof(debug_dir) * sizeof(char), debug_dir) != 0) { + strcat(debug_dir, "pidgin.RPT"); + printf(" Setting exchndl.dll LogFile to %s\n", + debug_dir); + (proc)(debug_dir); + } + } + proc = GetProcAddress(hmod, "SetDebugInfoDir"); + if (proc) { + char *pidgin_dir_ansi = NULL; + tmp[0] = _T('\0'); +#ifdef _UNICODE + i = WideCharToMultiByte(CP_ACP, 0, pidgin_dir, + -1, NULL, 0, NULL, NULL); + if (i != 0) { + pidgin_dir_ansi = malloc(i * sizeof(char)); + i = WideCharToMultiByte(CP_ACP, 0, pidgin_dir, + -1, pidgin_dir_ansi, i, NULL, NULL); + if (i == 0) { + free(pidgin_dir_ansi); + pidgin_dir_ansi = NULL; + } + } +#else + pidgin_dir_ansi = pidgin_dir; +#endif + if (pidgin_dir_ansi != NULL) { + _snprintf(debug_dir, sizeof(debug_dir) / sizeof(char), + "%s\\pidgin-%s-dbgsym", + pidgin_dir_ansi, VERSION); + debug_dir[sizeof(debug_dir) / sizeof(char) - 1] = '\0'; + printf(" Setting exchndl.dll DebugInfoDir to %s\n", + debug_dir); + (proc)(debug_dir); +#ifdef _UNICODE + free(pidgin_dir_ansi); +#endif + } + } - strcat(pidgin_dir, "\\exchndl.dll"); - if (LoadLibrary(pidgin_dir)) - printf("Loaded exchndl.dll\n"); + } - prev[0] = '\0'; + tmp[0] = _T('\0'); } } else { DWORD dw = GetLastError(); - const char *err_msg = get_win32_error_message(dw); - _snprintf(errbuf, 512, - "Error getting module filename.\nError: (%u) %s", + const TCHAR *err_msg = get_win32_error_message(dw); + _sntprintf(errbuf, 512, + _T("Error getting module filename.\nError: (%u) %s"), (UINT) dw, err_msg); - printf("%s\n", errbuf); + _tprintf(_T("%s\n"), errbuf); MessageBox(NULL, errbuf, NULL, MB_OK | MB_TOPMOST); - pidgin_dir[0] = '\0'; + pidgin_dir[0] = _T('\0'); } /* Determine if we're running in portable mode */ - if (strstr(lpszCmdLine, "--portable-mode") - || (exe_name != NULL && strstr(exe_name, "-portable.exe"))) { - int i = 0, c = 0; - + if (wcsstr(cmdLine, L"--portable-mode") + || (exe_name != NULL && _tcsstr(exe_name, _T("-portable.exe")))) { printf("Running in PORTABLE mode.\n"); portable_mode = TRUE; - - /* Remove the --portable-mode arg from the args passed to pidgin so it doesn't choke */ - pidgin_argv = malloc(sizeof(char*) * pidgin_argc); - for (; i < __argc; i++) { - if (strstr(__argv[i], "--portable-mode") == NULL) - pidgin_argv[c++] = __argv[i]; - else - pidgin_argc--; - } } if (portable_mode) portable_mode_dll_prep(pidgin_dir); else if (!getenv("PIDGIN_NO_DLL_CHECK")) - dll_prep(); + dll_prep(pidgin_dir); winpidgin_set_locale(); @@ -741,23 +773,53 @@ return 0; /* Now we are ready for Pidgin .. */ - if ((hmod = LoadLibrary("pidgin.dll"))) + if ((hmod = LoadLibrary(_T("pidgin.dll")))) pidgin_main = (LPFNPIDGINMAIN) GetProcAddress(hmod, "pidgin_main"); if (!pidgin_main) { DWORD dw = GetLastError(); BOOL mod_not_found = (dw == ERROR_MOD_NOT_FOUND || dw == ERROR_DLL_NOT_FOUND); - const char *err_msg = get_win32_error_message(dw); + const TCHAR *err_msg = get_win32_error_message(dw); - _snprintf(errbuf, 512, "Error loading pidgin.dll.\nError: (%u) %s%s%s", + _sntprintf(errbuf, 512, _T("Error loading pidgin.dll.\nError: (%u) %s%s%s"), (UINT) dw, err_msg, - mod_not_found ? "\n" : "", - mod_not_found ? "This probably means that GTK+ can't be found." : ""); - printf("%s\n", errbuf); - MessageBox(NULL, errbuf, TEXT("Error"), MB_OK | MB_TOPMOST); + mod_not_found ? _T("\n") : _T(""), + mod_not_found ? _T("This probably means that GTK+ can't be found.") : _T("")); + _tprintf(_T("%s\n"), errbuf); + MessageBox(NULL, errbuf, _T("Error"), MB_OK | MB_TOPMOST); return 0; } + /* Convert argv to utf-8*/ + szArglist = CommandLineToArgvW(cmdLine, &j); + pidgin_argc = j; + pidgin_argv = malloc(pidgin_argc* sizeof(char*)); + k = 0; + for (i = 0; i < j; i++) { + success = FALSE; + /* Remove the --portable-mode arg from the args passed to pidgin so it doesn't choke */ + if (wcsstr(szArglist[i], L"--portable-mode") == NULL) { + int len = WideCharToMultiByte(CP_UTF8, 0, szArglist[i], + -1, NULL, 0, NULL, NULL); + if (len != 0) { + char *arg = malloc(len * sizeof(char)); + len = WideCharToMultiByte(CP_UTF8, 0, szArglist[i], + -1, arg, len, NULL, NULL); + if (len != 0) { + pidgin_argv[k++] = arg; + success = TRUE; + } + } + if (!success) + wprintf(L"Error converting argument '%s' to UTF-8\n", + szArglist[i]); + } + if (!success) + pidgin_argc--; + } + LocalFree(szArglist); + + return pidgin_main(hInstance, pidgin_argc, pidgin_argv); }
--- a/po/ChangeLog Mon Nov 02 23:14:33 2009 +0000 +++ b/po/ChangeLog Wed Nov 11 03:07:21 2009 +0000 @@ -2,8 +2,11 @@ version 2.7.0 +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)
--- a/po/POTFILES.in Mon Nov 02 23:14:33 2009 +0000 +++ b/po/POTFILES.in Wed Nov 11 03:07:21 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
--- a/po/ca.po Mon Nov 02 23:14:33 2009 +0000 +++ b/po/ca.po Wed Nov 11 03:07:21 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ó <josep.puigdemont@gmail.com>\n" "Language-Team: Catalan <tradgnome@softcatala.net>\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 "<AUTO-REPLY> " msgstr "<RESPOSTA-AUTOMÀTICA> " @@ -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 <b>English</b>. You are welcome to post in another " "language, but the responses may be less helpful.<br/><br/>" msgstr "" +"<font size=\"4\">Ajuda d'altres usuaris del Pidgin:</font> <a href=\"mailto:" +"support@pidgin.im\">support@pidgin.im</a><br/>Aquesta és una llista de " +"correu <b>pública</b>. (<a href=\"http://pidgin.im/pipermail/support/" +"\">arxiu</a>)<br/>No us podem ajudar amb connectors d'altres proveïdors.<br/" +">En aquesta llista s'hi empra principalment l'<b>anglès</b>. Podeu escriure-" +"hi en un altre idioma, però és possible que les respostes no siguin de gaire " +"ajuda.<br/><br/>" #, 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 "<span style=\"italic\">Example: stunserver.org</span>" msgstr "<span style=\"italic\">Exemple: stunserver.org</span>" @@ -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"